MHebes
MHebes

Reputation: 3247

How do I display a C++/WinRT FileOpenPicker from an exe without a console or window?

From a C++ executable on windows, I want to display a FileOpenPicker.

To do this, I need a window to set as the object's owner: https://learn.microsoft.com/en-us/windows/apps/develop/ui-input/display-ui-objects#winui-3-with-c

But where do I get the HWND from? I need to call Initialize, but the docs assume you have a hWnd already:

folderPicker.as<::IInitializeWithWindow>()->Initialize(hWnd);

My process does not always have a console window, so ::GetConsoleWindow() will not work.

Here's what I have so far by attempting to use CreateWindow. Nothing happens.

// clang-format off
#include <ShObjIdl.h>
#include <Windows.h>
// clang-format on

#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Storage.Pickers.h>

#include <iostream>
#include <string>
#include <string_view>
#include <system_error>

LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
  switch (msg) {
    case WM_DESTROY:
      ::PostQuitMessage(0);
      return 0;
  }
  return ::DefWindowProc(hWnd, msg, wParam, lParam);
}

int main() {
  try {
    winrt::init_apartment();

    winrt::Windows::Storage::Pickers::FileOpenPicker openPicker;

    WNDCLASSEX wc = {sizeof(WNDCLASSEX),    CS_CLASSDC, WndProc, 0L,   0L,
                     GetModuleHandle(NULL), NULL,       NULL,    NULL, NULL,
                     L"ImGui Example",      NULL};
    ::RegisterClassEx(&wc);
    HWND hwnd = ::CreateWindow(
        wc.lpszClassName, L"Dear ImGui DirectX10 Example", WS_OVERLAPPEDWINDOW,
        100, 100, 1280, 800, NULL, NULL, wc.hInstance, NULL);

    if (!hwnd) {
      std::cerr
          << std::error_code(::GetLastError(), std::system_category()).message()
          << "\n";
      return 1;
    }

    openPicker.as<::IInitializeWithWindow>()->Initialize(hwnd);
    openPicker.SuggestedStartLocation(
        winrt::Windows::Storage::Pickers::PickerLocationId::Desktop);
    openPicker.FileTypeFilter().Append(L"*");
    openPicker.FileTypeFilter().Append(L".jpeg");
    openPicker.FileTypeFilter().Append(L".png");
    auto file = openPicker.PickSingleFileAsync().get();
    std::string str = winrt::to_string(file.Path());
    std::cout << "name: " << str << "\n";

    return 0;
  } catch (std::exception& e) {
    std::cerr << "error: " << e.what() << "\n";
    return 1;
  }
}

This question appears to be asking a similar thing, but OP solved it with ::GetConsoleWindow(), which is not an option.

Upvotes: 4

Views: 1089

Answers (2)

DengTao
DengTao

Reputation: 1

    // 0. Header files: 
    // <windows.ui.xaml.window.h>
    // <ShObjIdl_core.h>

    // 1. Get Window HWND
    auto windowNative{ this->m_inner.as<::IWindowNative>() };
    HWND hWnd{ 0 };
    windowNative->get_WindowHandle(&hWnd); 

    // 2. Initialize folder picker 
    FolderPicker folderPicker{};
    folderPicker.as<::IInitializeWithWindow>()->Initialize(hWnd);

Upvotes: -1

IInspectable
IInspectable

Reputation: 51511

Based on empirical evidence, IInitializeWithWindow::Initialize() is happy with just about any top-level window handle. A window handle associated with a process running in the CONSOLE subsystem is commonly available through the GetConsoleWindow API:

openPicker.as<::IInitializeWithWindow>()->Initialize(::GetConsoleWindow());

This produces the desired behavior1 when using the standard command prompt (hosted by ConHost.exe). Launching the same program from a process using a pseudoconsole instead (such as Windows Terminal) things start to get flaky: The FilePicker does show up, but it no longer follows the rules of owned windows. It's hiding behind the host process' window, and doesn't move to the foreground, when the (not actually an) owner is activated.

Whether this is how things should work, or are designed to work, or if any of this is within specification is unclear. None of this is documented, which seems to be the most convenient way to ship interfaces these days.

The fact that FileOpenPicker isn't documented to implement IInitializeWithWindow doesn't exactly help, either. All hail to cloaked interfaces of which there is only anecdotal evidence.

Ranting aside, you'll probably want to use std::wcout in place of std::cout. Otherwise you'll see an address printed rather than a string.


1 Just because you can doesn't necessarily mean you should create a window hierarchy across threads.

Upvotes: 1

Related Questions