65.9K
CodeProject 正在变化。 阅读更多。
Home

在现有 Win32 应用程序中使用 WinUI3

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (18投票s)

2023年4月24日

CPOL

8分钟阅读

viewsIcon

31823

如何在不进行打包的情况下, 在普通 Win32 应用程序中使用 WinUI3 控件

引言

实话实说。如果微软创造了与纯 Win32 API 兼容的东西,大家仍然会使用这个旧 API,偶尔尝试新界面。因此,他们费尽心机让我难以使用他们发明的任何新东西。

UWP 未能在 Win32 中普及。 他们尝试了 XAML Islands - 我创建了一个小型库(尝试)使用它。它不支持所有控件,即使是支持的控件也充满了糟糕的 Bug。不能用于正式的生产环境。没关系。

我曾认为唯一的方法是使用两个可执行文件。然而,Sota Nakamura 的一篇精彩文章指引了我正确的方向。你可以有一个仍然兼容 Windows 7 的应用程序,并且可以立即使用 WinUI3。

所以,让我向你展示我是如何做到的。这是我的普通 Win32 Direct2D 应用Turbo Play的截图,以及一个 WinUI3 的“是、否、取消”对话框。

这是这个项目的一个小型演示。一个 Win32 对话框调用一个 WinUI 对话框。

WUI3 项目

  1. 在 Visual Studio 中创建一个新的“打包的 WinUI 3 空白应用”项目(在“桌面”类别下)。
  2. 关闭项目,并用文本编辑器打开 .vcxproj 文件。
  3. <AppxPackage>true</AppxPackage> 更改为 <AppxPackage>false</AppxPackage>
  4. 在第一个 <PropertyGroup Label="Globals"> 之后添加元素 <WindowsPackageType>None</WindowsPackageType>
  5. 用 Visual Studio 重新打开项目。
  6. 转到工具 -> NuGet 包管理器 -> 管理解决方案的 NuGet 程序包,然后更新四个程序包。这可确保使用最新的 WinUI3 库版本。

这可确保你创建的是一个“未打包”的项目。这个项目的唯一用途是编译你的 XAML 文件为 XBF 文件。除此之外没有其他用途。

编辑你的 MainWindow.xaml 文件(或你添加到其中的任何其他 XAML 文件)并进行编译。你应该会在 \x64\Debug\embed 目录下看到 MainWindow.xbf

普通应用程序

  1. 创建一个新的标准 Win32 项目。

  2. 将标准设置为 C++ 17。

  3. 可选地将其设置为静态(无 DLL 运行时)并添加

    #pragma comment(linker,"\"/manifestdependency:type='win32' 
    	\ name='Microsoft.Windows.Common-Controls' version='6.0.0.0' 
    	\ processorArchitecture='*' publicKeyToken='6595b64144ccf1df' 
    	language='*'\"") 

    用于样式。

  4. 使用 Nuget 安装 WindowsAppSDKMicrosoft.Windows.CppWinRT

  5. 添加一个 RC 文件,其中将包含 xbf 文件作为资源。

    L1 DATA "..\\wui3\\x64\\debug\\embed\\MainWindow.xbf"
  6. 间接调用引导程序。
        CoInitializeEx(0, COINIT_APARTMENTTHREADED);
        PACKAGE_VERSION pg = {};
        typedef HRESULT (__stdcall* mi)(
            UINT32 majorMinorVersion,
            PCWSTR versionTag,
            PACKAGE_VERSION minVersion);
        const wchar_t* dll = L"Microsoft.WindowsAppRuntime.Bootstrap.dll";
        auto hL = LoadLibrary(dll);
        mi M = (mi)GetProcAddress(hL, "MddBootstrapInitialize");
        if (!M)
            return E_FAIL;
        auto hr = M(0x00010003, L"", pg);

    现在引导程序已加载。

创建“App”类

让我们首先看看如何构建资源。你必须向加载器提供 ms-appx://local/<path> 文件名,其中包含你保存在 RC 文件中的所有 XBF 资源。

void BuildURLS()
    {
        urls.clear();
        wchar_t x[200] = {};
        auto tf = TempFile4(0,0,0);
        tf += L".xbf";
        ExtractResourceToFile(GetModuleHandle(0), L"L1", L"DATA", tf.c_str());
        swprintf_s(x, 200, L"ms-appx://local/%s", tf.c_str());
        urls.push_back(x);
    }

现在让我们看看 App 类。

bool FirstRun = 1;
class AppL : public ApplicationT<AppL, IXamlMetadataProvider>
{
    XamlControlsXamlMetaDataProvider provider;
public:
    std::vector<std::wstring> urls;
    std::vector<Window> windows;
    void BuildURLS() {...}
    void L2()
    {
        BuildURLS();
        if (FirstRun)
        {
            Resources().MergedDictionaries().Append(XamlControlsResources());
            for (size_t i = 0; i < urls.size(); i++)
            {
                windows.emplace_back(Window());
            }
            for (size_t i = 0; i < urls.size(); i++)
            {
                Application::LoadComponent(windows[i], Uri(urls[i].c_str()));
            }
            FirstRun = 0;
            windows[0].Activate();
        }
        else
        {
            for (size_t i = 0; i < urls.size(); i++)
            {
                windows[i].Activate();
                windows[i].Activated = 1;
            }
        }
    }
    void OnLaunched(LaunchActivatedEventArgs const&)
    {
        L2();
    }
    IXamlType GetXamlType(TypeName const& type)
    {
        return provider.GetXamlType(type);
    }
    IXamlType GetXamlType(hstring const& fullname)
    {
        return provider.GetXamlType(fullname);
    }
    com_array<XmlnsDefinition> GetXmlnsDefinitions()
    {
        return provider.GetXmlnsDefinitions();
    }
};

首先,你使用 IXamlMetadataProvider 的回调来为 WinUI3 提供“视觉样式”(如果这样做,你将获得旧的 UWP 样式)。

其次,当 OnLaunch 被调用时,你必须加载所有窗口,因为你不能多次调用 Resources().MergedDictionaries().Append(XamlControlsResources());。无论你稍后 Activate() 还是隐藏/显示你的窗口,都取决于你,但必须立即加载到所有窗口。

在你的 WinMain 中现在

auto app3 = make<AppL>();
Application::Start([&](auto&&) {
    app3;
    });

只要有窗口可见,这就不会返回。如果返回,你可以再次调用它。

安装可再发行组件

对于未打包的应用程序,在运行它之前,你必须以管理员身份安装可再发行组件。从这个链接获取可再发行组件。

你已准备就绪并正在运行!

更多想法

你还不能在 XAML 中定义事件。你必须手动设置它们。

Panel p = Content().as<Panel>();
p.FindName(L"myButton").as<Button>().Click([&]
          (IInspectable const&, RoutedEventArgs const&)
    {
        MessageBox(0, L"Clicked", 0, 0);
    });

你可以子类化 WinUI 窗口以在自己的 Window PRoc 中手动处理消息。

// Subclass
auto n = as <IWindowNative>();
if (n)
{
    n->get_WindowHandle(&hwnd);
    old = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
    SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)NEWP);
}

之后,你可以像使用任何普通窗口一样使用 SendMessage

自动调整窗口大小

auto co = Content();
Panel sp = FindElementByName(L"rsp2").as<Panel>();
if (!Activated)
{
    sp.SizeChanged([&](winrt::Windows::Foundation::IInspectable const& sender,
                           winrt::Windows::Foundation::IInspectable)
    {
        OnChangeSize(sender, false);
    });

你想让窗口自动适应,所以我把一个第二个 StackPanel 放在第一个里面,名字叫 rsp2,并在它上面触发一个 OnChangeSize

void OnChangeSize(winrt::Windows::Foundation::IInspectable const& sender, bool f)
{
    auto dlg = sender.as<winrt::Microsoft::UI::Xaml::Controls::StackPanel>();
    auto strn = dlg.Name();
    float xy = GetDpiForWindow(hwnd) / 96.0f;
    auto wi5 = dlg.ActualWidth() * xy;
    auto he5 = dlg.ActualHeight() * xy;
    wi5 += GetThemeSysSize(0, SM_CXBORDER);
    he5 += GetSystemMetrics(SM_CYCAPTION) + GetSystemMetrics(SM_CYSIZEFRAME) + 
       GetSystemMetrics(SM_CYEDGE) * 2;
    SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, (int)wi5, (int)he5, 
             SWP_SHOWWINDOW | SWP_NOMOVE);
CenterWindow2(hwnd);
}

将 Win32 控件托管到 WinUI3 窗口中

这个很有帮助。你必须将窗口设置为 WS_EX_LAYERED,然后调用                 SetLayeredWindowAttributes(hwnd, 0, (BYTE)(255 * 100 / 100), LWA_ALPHA);

这仍然需要一些工作(例如,加速转发等),但它存在一个 Bug(WinUI3 控件无法绘制在你的 Win32 窗口之上 - 菜单有问题)。

基本规则是,Win32 窗口将覆盖任何 WinUI3 控件。这意味着你必须将任何 WinUI 放置在主窗口的顶部、左侧、右侧、底部,并将任何 Win32 HWND 放在里面。我为菜单所做的是处理菜单项的点击并在其下方显示一个普通的 HMENU - 呃,太丑了,但目前我无能为力。

这是具有完整 WinUI3 模式的 Turbo Play。

 

旧文章

仅供参考,了解使用 Winui3 的旧方法。现在不需要了。

启动一个不可见的应用程序

WinUI3 应用程序只能有一个窗口,一旦它被销毁,你就无法再次创建它。因此,你的 WinUI3 应用程序将只有一个窗口,其中包含一个大的 XAML,用于显示你所有的 UI 并按需切换它们。

在你的 OnLaunched 事件中,执行以下操作。

auto ew = make<MainWindow>();
window = ew;
window.Activate();
if (window)
{
    auto n = window.as<IWindowNative>();
    if (n)
    {
        n->get_WindowHandle(&mw);
        if (mw)
        {
            ShowWindow(mw, SW_HIDE);
            auto ew2 = window.as<MainWindow>();
            ew2->Subclass();
            std::thread t(tx, this);
            t.detach();
        }
    }
}

你需要获取一个 HWND 以供以后在进程间使用(保存在 mw 中),然后你需要通过新的窗口过程来子类化这个窗口(稍后详述)。

Old = (WNDPROC)GetWindowLongPtr(mw, GWLP_WNDPROC);
SetWindowLongPtr(mw, GWLP_WNDPROC, (LONG_PTR)NEWW_WP);
mwt = this;

然后,你想要创建一个线程来等待你的 Win32 应用程序的触发。

void tx(App* app)
{
    if (!app)
        return;
    auto w = app->window.as<winrt::wui3::implementation::MainWindow>();
    w->Run();
}

我们稍后会研究这个 Run() 函数。

创建 XAML

如前所述,你只能有一个 XAML。所以我在这里创建了两个条目:一个 infobox(用于替换 MessageBox)和一个 AskText 对话框。

<StackPanel Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" 
RequestedTheme="Dark" KeyDown="KeyD2">

    <!-- Ask Text -->
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" 
    VerticalAlignment="Center" x:Name="AskText" Visibility="Collapsed" 
    SizeChanged="szch" Width="500">
        <StackPanel MinWidth="500">
            <InfoBar Name="AskText_Question" IsOpen="True" 
                Severity="Informational"  Title=""  IsIconVisible="False" 
                IsClosable="False"  Message="" />
            <TextBox Name="AskText_Response" Margin="15" 
                Text="" KeyDown="KeyD"/>
            <StackPanel Orientation="Horizontal" Margin="15" 
                HorizontalAlignment="Right">
                <Button Content=""  Margin="0,0,0,5" 
                    Name="AskText_OK"    Click="AskText_ClickOK" 
                    Style="{ThemeResource AccentButtonStyle}"  />
                <Button Content="" Margin="15,0,0,5" 
                Name="AskText_Cancel" Click="AskText_ClickCancel" />
            </StackPanel>
        </StackPanel>
    </StackPanel>

    <!-- Message -->
    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" 
    VerticalAlignment="Center" x:Name="Message1" Visibility="Collapsed" 
    SizeChanged="szch" Width="500">
       <StackPanel MinWidth="500">
           <InfoBar MinHeight="100" Name="Message1_Question" 
               IsOpen="True"  Severity="Informational"  Title=""  
               IsIconVisible="False"  IsClosable="False"  Message="" />
           <StackPanel Orientation="Horizontal" Margin="15" 
               HorizontalAlignment="Left">
           <Button Content=""  Margin="0,0,0,5" 
               Name="Message1_OK"    Click="AskText_ClickCancel" 
               Style="{ThemeResource AccentButtonStyle}"  />
       </StackPanel>
       </StackPanel>
    </StackPanel>
    
</StackPanel>

这两个 StackPanel 都设置了 Visibility="Collapsed"SizeChanged="szch",因为我们需要调整主窗口的大小以使其与第一个调用时 StackPanel 自动调整的大小相同。

等待触发

现在让我们看看那个 Run 函数。它将等待我们的主可执行文件发送通知以显示对话框。

首先,我们检查 HwndOfWin32App。如果由于任何原因它失效,WinUI3 应用程序将终止。然后,我们也向 Win32 应用程序返回我们自己的 HWND 句柄,并触发一个等待事件,以便我们的 Win32 应用程序知道 WinUI 应用程序已准备好。对于所有这些通信,我使用了我的 USM 库(包含在此存储库中)。

然后我们等待主应用程序的触发。如果超时并且 hwnd 不再有效,我们将退出,否则继续等待。

如果我们收到触发,我们将读取一个 DIALOGID(在 common.h 中找到,我已经定义了 DIALOGID_ASKTEXTDIALOGID_MESSAGE),目前是这样。这还包括读取一个 XML string(使用我自己的xml3all.h库),以便我们知道如何初始化我们的对话框。然后,我们向我们的窗口发送一个 WM_APP 消息来加载它(必须来自主线程!)。

// Create the mutex 
u = std::make_shared<USMLIBRARY::usm<>>(usm_cid, 0, 1024 * 1024, 10);
u->Initialize();
u->ReadData((char*)&HwndOfWin32App, 8, 0);
SetTimer(mw, 1, 500, 0);
u->WriteData((char*)&mw, sizeof(HWND), 0, 0);
SetEvent(u->hEventAux1);
for (;;)
{
    auto j = u->NotifyWrite(true, 5000);
    if (j == WAIT_TIMEOUT)
    {
        if (!IsWindow((HWND)HwndOfWin32App))
            break;
    }
    else
    {
        auto id = DIALOGID_NONE;
        auto rd = u->BeginRead();
        if (!rd)
            continue;
        unsigned long long xlen = 0;
        memcpy(&id, rd + 0, sizeof(id));
        memcpy(&xlen, rd + sizeof(id), sizeof(xlen));
        std::vector<char> xmld;
        if (xlen < (1024 * 1024))
        {
            xmld.resize(xlen + 1);
            memcpy(xmld.data(), rd + sizeof(id) + sizeof(xlen), xlen);
        }
        u->EndRead();
        if (xlen < (1024 * 1024))
            SendMessage(mw, WM_APP, id, (LPARAM)xmld.data());
    }
}
if (mw)
    PostMessage(mw, WM_CLOSE, 0xFEFEFEFE, 0);

如果出现问题,我们发送一个 WM_CLOSE 消息以及 0xFEFEFEFE 标志来终止应用程序。我们需要这个标志,因为如果用户按下X按钮,会发送一个没有这个标志的 WM_CLOSE,我们不能将其传递给旧的窗口过程(否则应用程序会崩溃)。

运行对话框

WM_APP 处理程序将使用传递的 ID 以及任何初始化 XML string 调用 RunDialog()

        unsigned long long id = mm - WM_APP;
        XML3::XML xx;
        if (ll)
            xx = (char*)ll;
        current_id = id;
        AskText().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
        Message1().Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
        if (id == DIALOGID_ASKTEXT)
        {
            XML3::XML* x = (XML3::XML*)&xx;
            Title(x->GetRootElement().vv("title").GetWideValue().c_str());
            AskText_Question().Title(x->GetRootElement().vv
                               ("t0").GetWideValue().c_str());
            AskText_Question().Message(x->GetRootElement().vv
                               ("t1").GetWideValue().c_str());
            AskText_Response().Text(x->GetRootElement().vv
                               ("t2").GetWideValue().c_str());
            AskText_Response().PlaceholderText
                   (x->GetRootElement().vv("t3").GetWideValue().c_str());

            if (x->GetRootElement().vv("big").GetValueInt())
            {
                AskText_Response().TextWrapping
                (winrt::Microsoft::UI::Xaml::TextWrapping::Wrap);
                AskText_Response().AcceptsReturn(true);
                AskText_Response().MinHeight(100);
            }
            AskText_Response().SelectAll();
            AskText_OK().Content(box_value(x->GetRootElement().vv
                                ("tOK").GetWideValue().c_str()));
            AskText_Cancel().Content(box_value(x->GetRootElement().vv
                             ("tCancel").GetWideValue().c_str()));

            SetWindowPos(mw, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | 
                         SWP_NOMOVE | SWP_NOSIZE);
            auto dlg = AskText();
            dlg.Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
            if (ChangedSizeOnce[current_id])
            {
                OnChangeSize(dlg, 1);
            }
        }

        if (id == DIALOGID_MESSAGE1)
        {
            XML3::XML* x = (XML3::XML*)&xx;
            Title(x->GetRootElement().vv("title").GetWideValue().c_str());
            Message1_Question().Title(x->GetRootElement().vv
                                ("t0").GetWideValue().c_str());
            Message1_Question().Message(x->GetRootElement().vv
                                ("t1").GetWideValue().c_str());
            Message1_OK().Content(box_value(x->GetRootElement().vv
                                 ("tOK").GetWideValue().c_str()));
            SetWindowPos(mw, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | 
                         SWP_NOMOVE | SWP_NOSIZE);
            auto dlg = Message1();
            dlg.Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
            if (ChangedSizeOnce[current_id])
            {
                OnChangeSize(dlg, 1);
            }
        }

我们在这里做什么?

  • 我们隐藏所有内容。
  • 我们使用传递的 XML string 来初始化对话框中的元素。
  • 我们显示对话框。
            SetWindowPos(mw, HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | 
                         SWP_NOMOVE | SWP_NOSIZE);
            auto dlg = AskText();
            dlg.Visibility(winrt::Microsoft::UI::Xaml::Visibility::Visible);
            if (ChangedSizeOnce[current_id])
            {
                OnChangeSize(dlg, 1);
            }

是的,我们需要将其设置为 TopMost 确实很丑陋,但我们希望它隐藏我们的主 Win32 应用程序。另外,如果这是我们第一次显示(ChangedSizeOnce[current_id] == 0),那么我们将等待控件调整大小,以便调用我们的 szch。如果这不是我们第一次调用它,我们必须通过手动调用 OnChangeSize 并传递一个强制参数来自己调整它。

更改大小

void MainWindow::OnChangeSize(winrt::Windows::Foundation::IInspectable const& sender, 
                              bool f) {
  if (f)
            ChangedSizeOnce[current_id] = 0;

        if (ChangedSizeOnce[current_id])
            return;
        ChangedSizeOnce[current_id] = 1;

        auto dlg = sender.as<winrt::Microsoft::UI::Xaml::Controls::StackPanel>();
        float xy = GetDpiForWindow(mw) / 96.0f;
        auto wi5 = dlg.ActualWidth() * xy;
        auto he5 = dlg.ActualHeight() * xy;
        wi5 += GetThemeSysSize(0, SM_CXBORDER);
        he5 += GetSystemMetrics(SM_CYCAPTION) + 
               GetSystemMetrics(SM_CYSIZEFRAME) + GetSystemMetrics(SM_CYEDGE) * 2;
        SetWindowPos(mw, HWND_TOPMOST, 0, 0, (int)wi5, (int)he5, 
                     SWP_SHOWWINDOW | SWP_NOMOVE);
        CenterWindow(mw);

如果我们正在强制调整大小,或者这是第一次,我们将获取 DPI 和主题边框长度,然后将 WinUI3 应用程序的主窗口调整为与加载的对话框大小匹配。

用户按下 X、ESC 或 Enter 键

   if (mm == WM_CLOSE && ww != 0xFEFEFEFE)
    {
        XML3::XML x;
        if (mwt->current_id == DIALOGID_ASKTEXT)
        {
            auto dlg = mwt->AskText();
            dlg.Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
        }
        if (mwt->current_id == DIALOGID_MESSAGE1)
        {
            auto dlg = mwt->Message1();
            dlg.Visibility(winrt::Microsoft::UI::Xaml::Visibility::Collapsed);
        }
        mwt->Cancel(x);
        return 0;
    } 

除非我们使用了 0xFEFEFEFE 标志,否则当用户按下X时,我们不希望应用程序关闭。相反,我们关闭对话框并调用 Cancel(),它会调用 Off()

按键也发生类似情况,无论是针对(整个对话框的)通用 StackPanel,还是当光标在 TextBox 内时。

void MainWindow::KeyD(winrt::Windows::Foundation::IInspectable const& sender, 
                      winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs r)
    {
        auto k = r.Key();
        if (k == winrt::Windows::System::VirtualKey::Enter)
        {
            if (current_id == DIALOGID_MESSAGE1)
            {
                Microsoft::UI::Xaml::RoutedEventArgs a;
                AskText_ClickCancel(sender, a);
            }
        }
        if (k == winrt::Windows::System::VirtualKey::Escape)
        {
            if (current_id == DIALOGID_MESSAGE1)
            {
                Microsoft::UI::Xaml::RoutedEventArgs a;
                AskText_ClickCancel(sender, a);
            }
        }
    }
void MainWindow::KeyD2(winrt::Windows::Foundation::IInspectable const& sender, 
                       winrt::Microsoft::UI::Xaml::Input::KeyRoutedEventArgs r)
    {
        auto k = r.Key();
        if (k == winrt::Windows::System::VirtualKey::Enter)
        {
            if (current_id == DIALOGID_ASKTEXT)
            {
                Microsoft::UI::Xaml::RoutedEventArgs a;
                AskText_ClickOK(sender, a);
            }
            if (current_id == DIALOGID_MESSAGE1)
            {
                Microsoft::UI::Xaml::RoutedEventArgs a;
                AskText_ClickCancel(sender, a);
            }
        }
        if (k == winrt::Windows::System::VirtualKey::Escape)
        {
            XML3::XML x;
            Cancel(x);
        }
    }

通知调用者

Off() 函数将任何 XML 信息写入共享内存。

void MainWindow::Off(XML3::XML& x)
    {
        ShowWindow(mw, SW_HIDE);
        auto s = x.Serialize();
        auto rd = u->BeginWrite();
        if (!rd)
            return;
        unsigned long long xlen = s.length();
        memcpy(rd, &xlen, sizeof(xlen));
        memcpy(rd + sizeof(xlen), s.data(), s.length());
        u->EndWrite();
        current_id = DIALOGID_NONE;
        SetEvent(u->hEventAux2);
    }

Win32 项目

例如,调用 AskText 对话框。

auto id = DIALOGID_ASKTEXT;
const char* x1 = R"(<?xml?><e title="Ask" t0="This is bold title" 
      t1="Enter value:" t2="100" t3="Placeholder text" 
      tOK="OK" tCancel="Cancel" />)";
unsigned long long wl = strlen(x1);
std::vector<char> what(1000);
memcpy(what.data(), &id, sizeof(id));
memcpy(what.data() + sizeof(id), &wl, sizeof(wl));
memcpy(what.data() + sizeof(id) + sizeof(wl), x1, wl);
ResetEvent(u.hEventAux1);
ResetEvent(u.hEventAux2);
u.WriteData((char*)what.data(), sizeof(id) + sizeof(wl) + wl, 0);

HANDLE h2[2] = { u.hEventAux1,u.hEventAux2 };
for (;;)
{
    auto wi = WaitForMultipleObjects(2, h2, false, 2000);
    if (wi == WAIT_OBJECT_0)
    {
        wmsg(hh);
        continue; // OK, dialog is showing
    }
    if (wi != (WAIT_OBJECT_0 + 1))
    {
        
        SetOffWUI3();
        // Fail here
    }
    break; // dialog ended
}

unsigned long long how = 0;
what.clear();
auto rd = u.BeginRead();
memcpy(&how, rd, sizeof(how));
if (how < 1024 * 1024)
{
    what.resize(how);
    memcpy(what.data(), rd + sizeof(how), how);
}
u.EndRead();
XML3::XML x;
what.resize(what.size() + 1);
x = what.data();

// Parse x to check return values of the dialogs

这是代码中比较糟糕的部分。你借助 USM 库发送 XML 信息,现在我有一个等待循环。

HANDLE h2[2] = { u.hEventAux1,u.hEventAux2 };
for (;;)
{
    auto wi = WaitForMultipleObjects(2, h2, false, 2000);
    if (wi == WAIT_OBJECT_0)
    {
        wmsg(hh);
        continue; // OK, dialog is showing
    }
    if (wi != (WAIT_OBJECT_0 + 1))
    {
        
        SetOffWUI3();
        // Fail here
    }
    break; // dialog ended
}

void wmsg(HWND hh)
{
    MSG msg;
    if (GetMessage(&msg, hh, 0, 0))
    {
        if (msg.message == WM_LBUTTONDOWN || msg.message == WM_LBUTTONUP || 
            msg.message == WM_RBUTTONDOWN || msg.message == WM_RBUTTONUP || 
            msg.message == WM_MOUSEMOVE || msg.message == WM_KEYDOWN || 
            msg.message == WM_KEYUP || msg.message == WM_PAINT)
            return;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

如果第一个事件被触发,这意味着对话框仍然处于活动状态。但在这种情况下,我们不仅会等待,还会收到一条消息,这样看起来我们的等待应用程序并没有卡住,但我们也想处理任何鼠标/键盘消息。

如果第二个事件被触发,则表示用户已关闭对话框。因此,我们可以从共享内存中读取输出。

如果发生超时,则表示 WinUI3 应用程序已崩溃/已停止运行/等等。因此,我们可以回退到 Win32 对话框。

打包

x64\release\wui3 中,有 wui3.exewui3.winmdresources.pri、其他资产和 Microsoft.WindowsAppRuntime.Bootstrap.dll。将整个 wui3 目录与你的应用程序一起打包和分发。

代码

GIT 存储库包含带有两个可执行项目(项目)的解决方案。尽情享受吧!

历史

  • 2023 年 4 月 26 日:新的文章,无需第二个可执行文件。
  • 2023 年 4 月 24 日:首次发布。
© . All rights reserved.