4

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.

MHebes
  • 2,290
  • 1
  • 16
  • 29
  • The answer was provided in a comment made by Raymond Chen, who also provided a link to the documentation. – Ken White Mar 29 '22 at 19:44
  • @KenWhite indeed, but that documentation assumes you have a HWND from somewhere else. The OP of that question did not have one, which is the situation I’m also in. – MHebes Mar 29 '22 at 19:47
  • The answer is in Raymond's comment; the OP of that question thanks Raymond for providing the solution in a comment that follows. – Ken White Mar 29 '22 at 19:49
  • @MHebes you can probably just use [`GetConsoleWindow()`](https://learn.microsoft.com/en-us/windows/console/getconsolewindow) in this case. Otherwise, create your own `HWND` via `CreateWindow/Ex()` – Remy Lebeau Mar 29 '22 at 20:31

1 Answers1

1

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.

IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • 3
    The window you initialize with should belong to your process. This means that the console window is a bad choice, since it belongs to conhost, not you. Create a window, and use that window to initialize the picker. Note that the picker will not be modal to the console. You aren't meant to cross the streams like that. Console apps use the console. GUI apps use the GUI. – Raymond Chen Mar 29 '22 at 23:25