I hope this falls within the realm of this forum:
I want to use the windows shell(?) to allow users to select a number of files before allowing my programme to do a few things to them. For this I found the MSDN sample "CommonFileDialogModes" - http://msdn.microsoft.com/en-us/library/windows/desktop/dd940350%28v=vs.85%29.aspx
In the sample under this class:
class CFileOpenBasketPickerCallback : public IFileDialogEvents, public IFileDialogControlEvents
they have this function:
// IFileDialogEvents
IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
{
// if this button is in the "Add" mode then do this, otherwise return S_OK
IFileOpenDialog *pfod;
HRESULT hr = pfd->QueryInterface(IID_PPV_ARGS(&pfod));
if (SUCCEEDED(hr))
{
IShellItemArray *psia;
hr = pfod->GetSelectedItems(&psia);
if (SUCCEEDED(hr))
{
ReportSelectedItems(pfd, psia);
psia->Release();
}
pfod->Release();
}
return S_FALSE; // S_FALSE keeps the dialog up; return S_OK to allow it to dismiss.
}
which calls:
void ReportSelectedItems(IUnknown *punkSite, IShellItemArray *psia)
{
DWORD cItems;
HRESULT hr = psia->GetCount(&cItems);
for (DWORD i = 0; SUCCEEDED(hr) && (i < cItems); i++)
{
IShellItem *psi;
hr = psia->GetItemAt(i, &psi);
if (SUCCEEDED(hr))
{
PWSTR pszName;
hr = GetIDListName(psi, &pszName);
// .. I've cut some of this out for the example
CoTaskMemFree(pszName);
}
psi->Release();
}
}
}
Now I know pszName contains the names of the files selected. So I can add some extra code in to write this to disk. That works fine. But I dont want to write it to disk. I want to pass it back to the original functions that called this. The arguments for ReportSelectedItems can be altered, but IFACEMETHODIMP OnFileOk(IFileDialog *pfd) cannot as it is inherited. Adding a vector& file_names to the argument will stop it compiling.
So how should I deal with this? I could use a global variable for file_names, but everything I am learning about programming is telling me not to. It would be a quick fix, but I worry that would encourage me to be lazy in the future. I find it difficult to read the windows code and I don't really want to delve too much into the details of it. I can't even find what is calling the OnFileOk function, even though I know it is from one of the two base classes.
Do I really need to work at understanding all the library code just to get this one function doing what I'd like? Is there an faster way of going about this?
So to summarize, how would I get information from this inherited function without using a global variable or writing to disk? As I mentined before, I don't have much of a grasp of the code I am working with. And for future reference, how should I deal with this type of situation? I use c++ and would like to avoid c# and c as much as possible.
Thanks as always.
It seems a fairly big omission for Microsoft to have left out any sort of user data associated with the IFileDialog callbacks, but that does seem to be the case.
I'm assuming that simply calling GetSelectedItems() once the dialog returns is something you don't want to do for some reason - because that would obviously be the simplest solution.
From a quick look at the docs one way you may be able to pass data back from the event callback is using the owner window that you pass to IFileDialog::Show() (which is actually IModalWindow::Show()).
In the event handler, you get given the IFileDialog* pointer. From this, you can QI the address of the IOleWindow interface which will give you the dialog's window:
IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
{
CComPtr<IOleWindow> pWindow;
if (SUCCEEDED(pfd->QueryInterface(IID_IOleWindow, reinterpret_cast<void**>(&pWindow))))
{
HWND hwndDlg;
if (SUCCEEDED(pWindow->GetWindow(&hwndDlg)))
{
HWND hwndOwner;
if (hwndOwner = GetWindow(hwndDlg, GW_OWNER))
{
// hwndOwner is the owner window of the dialog
}
}
}
// more code
}
Now assuming that hwndOwner is your own window, you can associate any data you like with it using SetProp()/GetProp() - so you could use this as a mechanism to pass data back from within the callback.
A simple solution was to add member data inside the inherited class and link it from the constructor:
class CFileOpenBasketPickerCallback : public IFileDialogEvents, public IFileDialogControlEvents
{
public:
CFileOpenBasketPickerCallback(vector<wstring>& files) : files_(files)
{
}
// functions
private:
vector<wstring>& files_;
};
When constructing the object
vector<std::wstring> files
CFileOpenBasketPickerCallback foacb(files);
And in IFACEMETHODIMP OnFileOk(IFileDialog *pfd)
ReportSelectedItems(pfd, psia, files_);
ReportSelectedItems is not a member so you can alter the arguments.
Related
I am trying to implement a custom Windows Media Foundation media sink for use in a UWP application that will use it via MediaCapture::PrepareLowLagRecordToCustomSinkAsync().
However, I am currently running into an issue where the ProcessSample() function of my IMFStreamSink is never called, even though I queue the event in my IMFMediaSink::OnClockStart().
I have followed the documentation here ( https://learn.microsoft.com/en-us/windows/win32/medfound/media-sinks ) and also taken a look at the "Simple Communication" sample.
Here is the output that I am getting, I have logged every function call and the HRESULT of every call that my sinks execute:
MyCaptureMediaSink::SetPresentationClock
OK: Clock->AddClockStateSink(this)
MyCaptureStreamSink::BeginGetEvent
OK: MediaEventQueue->BeginGetEvent(pCallback, punkState)
MyCaptureMediaSink::OnClockStart
MyCaptureStreamSink::QueueEvent
OK: MediaEventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue)
OK: Sink->QueueEvent(MEStreamSinkStarted, GUID_NULL, S_OK, nullptr)
MyCaptureStreamSink::QueueEvent
OK: MediaEventQueue->QueueEventParamVar(met, guidExtendedType, hrStatus, pvValue)
OK: Sink->QueueEvent(MEStreamSinkRequestSample, GUID_NULL, S_OK, nullptr)
What I think I should be seeing after the last line is a call to MyCaptureStreamSink::EndGetEvent() due to MEStreamSinkStarted being received, followed by another BeginGetEvent() and EndGetEvent() pair and a call ProcessSample() because the next event would be the MEStreamSinkRequestSample event.
Am I missing some functions that I still need to call in order to get a call to these functions by the MediaCapture system?
I have found the solution to my problem:
I re-implemented my stream sink without using winrt::implements, which seems to work as intended. What I assume (I'm still not 100% sure, though) is happening is that since IMFStreamSink derives from IMFMediaGenerator, the winrt::implemnets does not generate the QueryInterface properly for the base type. In my own implementation, I need to explicitly handle it.
This is the old class declaration, which doesn't work:
class MyStreamSink : public winrt::implements<MyStreamSink, IMFStreamSink>
{
// ...
};
and this is the class that works:
class MyStreamSink: public IMFStreamSink
{
virtual HRESULT QueryInterface(const IID& riid, void** ppvObject) override
{
if (riid == __uuidof(IMFMediaEventGenerator))
{
*ppvObject = static_cast<IMFMediaEventGenerator*>(this);
}
//...
}
}
An even better approach would be to override winrt::is_guid_of. By this approach you get one shared v-table and not two separate v-tables. For more information: How do I use C++/WinRT to implement a classic COM interface that derives from another classic COM interface
template<>
inline bool winrt::is_guid_of<IMFStreamSink>(guid const& id) noexcept
{
return winrt::is_guid_of<IMFStreamSink, IMFMediaEventGenerator>(id);
}
class MyStreamSink : public winrt::implements<MyStreamSink, IMFStreamSink>
{
//...
};
I need to search for maximized windows from Win32 (by using EnumWindows) but I also want to filter for windows which are on the current virtual desktop. On MSDN, I found a page about the IVirtualDesktopManager interface but there seems to be no information on how to use this interface.
IVirtualDesktopManager::IsWindowOnCurrentVirtualDesktop(/*args...*/);
Throws the following error:
Non static member reference must be relative to a specific object
VirtualDesktopManager mVirtualDeskManager;
mVirtualDesktopManager.IsWindowOnCurrentVirtualDesktop(/args...*/)
Throws this error:
Incomplete type is not allowed
I have only found solutions on using the IVirtualDesktopManager interface in C# yet.
IVirtualDesktopManager is a COM interface. You need to instantiate the COM object that implements the interface.
Based on code from this blog, you can use IServiceProvider to access IVirtualDesktopManager (and IVirtualDesktopManagerInternal, which has much more functionality than IVirtualDesktopManager has), eg:
IServiceProvider* pServiceProvider = NULL;
HRESULT hr = ::CoCreateInstance(
CLSID_ImmersiveShell, NULL, CLSCTX_LOCAL_SERVER,
__uuidof(IServiceProvider), (PVOID*)&pServiceProvider);
if (SUCCEEDED(hr))
{
IVirtualDesktopManager *pDesktopManager = NULL;
hr = pServiceProvider->QueryService(__uuidof(IVirtualDesktopManager), &pDesktopManager);
if (SUCCEEDED(hr))
{
BOOL bIsOnCurrentDesktop = FALSE;
hr = pDesktopManager->IsWindowOnCurrentVirtualDesktop(hWnd, &bIsOnCurrentDesktop);
if (SUCCEEDED(hr))
{
// use bIsOnCurrentDesktop as needed...
}
pDesktopManager->Release();
}
pServiceProvider->Release();
}
I'm trying to figure out if it is possible to use OR re-create the CFolderPickerDialog dialog without using MFC, or if there has been an attempt. So far I did not find a lot of hints. This old question does not seem to help me either.
I currently open the normal folder dialog with SHBrowseForFolder. But I need an Explorer-style dialog.
Here is the Explorer-style dialog (MFC) from another application:
#include <afxdlgs.h> requires MFC. I cannot use MFC in this specific project.
Is there a way to do this without using MFC ?
Honestly, I didn't even know that MFC had wrapped this. My class library has its own implementation. And, as Barmak points out, the MFC implementation may even be buggy, and certainly has usage caveats that would not be obvious had you failed to read the documentation carefully.
That said, in general, it is good advice to use functionality that is already wrapped up in a library because this makes your life easier. If you don't want to use the whole library, but still want to see how it implements a particular feature, you can check the library's source code. MFC is provided with reference source so that you can do this easily (also so you can debug it). Although it would probably be a violation of the license to copy and paste code directly out of MFC (it would also be nigh-impossible, since it uses so many MFC-specific idioms), you can look at the code to see what they're doing, then go back to the Windows SDK documentation to figure out how to write the code yourself.
In this case, the relevant SDK documentation is here. Modern versions of Windows (since Vista) use the Common Item Dialog API to display open/save file/folder dialogs. The API consists of a base IFileDialog interface, with two sub-interfaces, IFileOpenDialog and IFileSaveDialog. There is a lot of flexibility here; the details are in the documentation, along with sample code.
Note that the Common Item Dialog is only available on Windows Vista and later. If you need to support older operating systems (I still support Windows XP), you need a fallback. The SHBrowseForFolder dialog is that fallback. It certainly has its design flaws, but it is better than nothing.
If all you want is a simple folder-picker dialog, here is an approximation of the code that I use. It uses a couple of ATL/MFC types, like the CString and CComPtr wrapper classes, but you can translate that to alternate classes of your own choosing (such as std::wstring and _com_ptr_t). It displays a simple browse-for-folder dialog, appropriate for the current operating system, with a caller-specified title and starting path. If it succeeds, it returns a string containing the path to the folder selected by the user; otherwise, it returns an empty string.
namespace
{
HRESULT Downlevel_SHCreateItemFromParsingName(PCWSTR pszPath, IBindCtx* pbc, REFIID riid, void** ppv)
{
_ASSERTE(IsWinVistaOrLater());
HRESULT hResult = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED);
const HINSTANCE hinstLib = GetModuleHandle(TEXT("shell32"));
if (hinstLib)
{
typedef HRESULT (WINAPI * pfSHCreateItemFromParsingName)(PCWSTR, IBindCtx*, REFIID, void**);
const pfSHCreateItemFromParsingName pf = reinterpret_cast<pfSHCreateItemFromParsingName>(GetProcAddress(hinstLib, _CRT_STRINGIZE(SHCreateItemFromParsingName)));
if (pf)
{
hResult = pf(pszPath, pbc, riid, ppv);
}
}
return hResult;
}
int CALLBACK BrowseForFolderCallbackProc(HWND hWnd, UINT uMsg, LPARAM /* lParam */, LPARAM lData)
{
if (uMsg == BFFM_INITIALIZED)
{
// Start with BFFM_SETSELECTION, which is always available.
SendMessage(hWnd, BFFM_SETSELECTION, TRUE, lData);
#ifdef UNICODE
// If possible, also try to use BFFM_SETEXPANDED, which was introduced with
// version 6.0 of the shell (Windows XP).
SendMessage(hWnd, BFFM_SETEXPANDED, TRUE, lData);
// You can also set the caption for the dialog's "OK" button here, if you like
// (e.g., by loading a string from a resource).
//SendMessage(hWnd,
// BFFM_SETOKTEXT,
// 0,
// reinterpret_cast<LPARAM>(pszOKBtnCaption));
#endif // UNICODE
}
return 0;
}
}
CString ShowFolderBrowserDialog(HWND hwndOwner, const CString& strDlgTitle, const CString& strStartPath)
{
if (IsWinVistaOrLater())
{
CComPtr<IFileOpenDialog> pFileOpenDlg;
if (SUCCEEDED(pFileOpenDlg.CoCreateInstance(__uuidof(FileOpenDialog))))
{
if (SUCCEEDED(pFileOpenDlg->SetTitle(strDlgTitle)))
{
FILEOPENDIALOGOPTIONS options;
if (SUCCEEDED(pFileOpenDlg->GetOptions(&options)))
{
if (SUCCEEDED(pFileOpenDlg->SetOptions(options | FOS_PATHMUSTEXIST | FOS_PICKFOLDERS | FOS_FORCEFILESYSTEM)))
{
CComPtr<IShellItem> psiStartPath;
if (SUCCEEDED(Downlevel_SHCreateItemFromParsingName(static_cast<const TCHAR*>(strStartPath),
NULL,
IID_PPV_ARGS(&psiStartPath))))
{
if (SUCCEEDED(pFileOpenDlg->SetFolder(psiStartPath)))
{
if (SUCCEEDED(pFileOpenDlg->Show(hwndOwner)))
{
CComPtr<IShellItem> pShellItemResult;
pFileOpenDlg->GetResult(&pShellItemResult);
CComHeapPtr<TCHAR> pszSelectedItem;
if (SUCCEEDED(pShellItemResult->GetDisplayName(SIGDN_FILESYSPATH, &pszSelectedItem)))
{
return pszSelectedItem;
}
}
}
}
}
}
}
}
}
else
{
TCHAR szBuffer[MAX_PATH + 1];
szBuffer[0] = TEXT('\0');
BROWSEINFO bi;
bi.hwndOwner = hwndOwner;
bi.pidlRoot = nullptr;
bi.pszDisplayName = szBuffer;
bi.lpszTitle = strDlgTitle;
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE | BIF_SHAREABLE | BIF_NONEWFOLDERBUTTON;
bi.lpfn = BrowseForFolderCallbackProc;
bi.lParam = reinterpret_cast<LPARAM>(static_cast<const TCHAR*>(strStartPath));
CComHeapPtr<ITEMIDLIST> pidl(SHBrowseForFolder(&bi));
if (pidl && SHGetPathFromIDList(pidl, szBuffer))
{
return pszSelectedItem;
}
}
return TEXT("");
}
The dialog only shows actual folders in the filesystem. Although the Common Item Dialog API supports other types of special folders and namespaces, I don't need that in my app, so my code doesn't deal with the complexity. Use this as a starting point, along with the documentation, if you need more features. The most notable aspect is probably the use of SHCreateItemFromParsingName (which I have wrapped up in a dynamic call so that the code continues to run on older operating systems) to translate the caller-specified starting path (which is a string) to a Shell item object (as required by the Common Item Dialog API).
I am passing a callback function to a library. What the callback essentially does is receive updates from the dll and send it to GUI to display. The problem is that since the callback is global or static function, it doesn't know about the GUI and who to pass which in my case will be dialog. The approach I have used to accomplish this is to use singleton and a proxy (sort of).
class CDispatcher
{
public:
CDispatcher(void);
~CDispatcher(void);
protected:
static HWND m_hWnd;
public:
static void SetWindow( HWND hWnd );
static void Dispatch(int code, char * msg);
};
Later in the code
BOOL CTestDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// I need to set this before I set callback
CDispatcher::SetWindow( m_hWnd );
//now I can set the callback
LibRegisterCallback( CDispatcher::Dispatch );
return TRUE; // return TRUE unless you set the focus to a control
}
While this works but first I don't know if my CDispatcher class is good module. It doesn't seem like a good singleton neither it looks like a good proxy. Maybe I could pass the handle of the window in constructor which would make it better but I don't if that's even possible since I am never instantiating the singleton. Another thing is I never how to instantiate CDispatcher because again its just a all global members.
Is this a case where proxy design pattern can be applied in a better way (I am guessing in conjuction with singleton)? Maybe another pattern solves this problem more elegantly? Or is my implementation fine?
While trying to create a nice wrapper around Win32 specific GUI components, I eventually ran into a problem. The problem is that I'm unable to close the application after the windows I created no longer exist.
My API works like this:
/// ----------------------------
/// #author God
/// #project Helixirr Widgets
/// ----------------------------
#include <helixirrwidgets/HelixirrWidgets.hpp>
int main(void){
HelixirrWidgets::Window __windows[2] = {HelixirrWidgets::Window("Big Window"), HelixirrWidgets::Window()};
__windows[0].position(200, 200);
__windows[0].size(800, 600);
__windows[0].visible(true);
__windows[0].save_changes();
__windows[1].name("Tiny Window");
__windows[1].position(10, 100);
__windows[1].size(400, 200);
__windows[1].visible(true);
__windows[1].save_changes();
while(__windows[0].active() || __windows[1].active()){
if(__windows[0].visible()){
__windows[0].show();
}
if(__windows[1].visible()){
__windows[1].show();
}
}
return 0;
}
In method of HelixirrWidgets::Window called "active", which is declared like this
inline bool active(void) const noexcept;
I can check, whether my window is active or not.
This method basically return a const reference to a boolean member variable of an instance. This member variable is modified in "show"-method of the same class. Here's the definition:
void Window::show(void){
if(GetMessage(&_m_opHelper->message, _m_opHelper->handle_window, 0, 0)){
if(_m_opHelper->message.message == WM_CLOSE){
_m_bActive = false;
return;
}
TranslateMessage(&_m_opHelper->message);
DispatchMessage(&_m_opHelper->message);
ShowWindow(_m_opHelper->handle_window, SW_SHOWDEFAULT);
UpdateWindow(_m_opHelper->handle_window);
_m_bActive = true;
return;
}
_m_bActive = false;
}
Do note I use pimpl-idiom to hide platform-specific structures ("_m_opHelper" is pointer to implementation).
It may look like it works, but it doesn't and I can't understand why. It all comes down to a simple question: how can I close my window implemented using WINAPI specific functions and structures to be closed appropriately by a user of my application?
I guess the cause of the issue is related to the fact WM_CLOSE simply is not last message HWND gets. Messages like WM_DESTROY, WM_NCDESTROY and possibly more (depending on the particlar window and its state) will come after WM_CLOSE, leading to the assignment _m_bActive = TRUE.
I.e. the window becomes inactive for very short time, and (likely) they will never be inactive at the same time, causing an endless loop in main().