vs +qt 怎么创建ui_为您的C ++ / QT应用程序创建一个很棒的WPF UI

本文针对C++/QT程序员,介绍如何在C++项目中利用WPF的丰富特性创建用户界面。作者讨论了现有的方法,如ActiveX和CLR Hosting的缺点,并提出一种基本方法,涉及在C++中创建主机窗口,用C#创建WPF GUI库,以及两者之间的交互。此外,还涵盖了处理宿主窗口句柄和安全性的技巧,以及将GUI打包到C++应用程序中的最佳实践。
摘要由CSDN通过智能技术生成

vs +qt 怎么创建ui

介绍 (Introduction)

I haven't written an article in a long long time, so, I'm here today with an awesome one. :)

我已经很长时间没有写文章了,所以,我今天在这里真棒。 :)

This article is for C++/QT programmers who want to use amazing features of WPF within their C++ code.

本文适用于希望在其C ++代码中使用WPF惊人功能的C ++ / QT程序员

If you're a .NET programmer, just walk away, it's not yours. (You can Use Invoking & DllImport stuff :v)

如果您是.NET程序员,请走开,那不是您的。 (您可以使用“调用和DllImport”内容:v)

背景与背景故事 (Background & Backstory)

When we talk about WPF, we talk about an easy-to-use, user-friendly & high performance framework created by Microsoft for .NET users.

在谈论WPF时,我们谈论的是Microsoft为.NET用户创建的易于使用,用户友好和高性能的框架。

It has the power of DirectX Renderer combined with HTML/CSS styling and it's open source! There are too many reasons which makes this little guy better than any other solution for Desktop Applications.

它具有DirectX Renderer的功能以及HTML / CSS样式 ,并且是开源的! 有太多的原因使这个小家伙比其他任何桌面应用程序解决方案都更好。

比QT,Winforms,HTML / CSS更好,这就是为什么... (Better than QT, Winforms, HTML/CSS, Here's why...)
  • QT Framework: It's not free if you need to use static linking. It has too many dependencies, also, it has poor component library and you need to buy third party libraries or you should create your components yourself.

    QT Framework :如果您需要使用静态链接,它不是免费的。 它具有太多的依赖关系,并且组件库很差,您需要购买第三方库,或者您应该自己创建组件。

  • Winforms: Very well designed, still a hero, but it's a little bit old and uses GDI+.

    Winforms :设计精良,仍然是英雄,但它有点旧,并使用GDI +。

  • HTML/CSS: Needs heavy sized dependencies like chromium, etc. You should ship 60+ MB for a simple hello world app developed in HTML/CSS and if you choose Electron, it comes in 80+ MB.

    HTML / CSS :需要大量依赖项,例如Chrome等。对于使用HTML / CSS开发的简单hello world应用程序,您应该交付60+ MB,如果选择Electron,则需要80+ MB。

  • WPF: Uses GPU Rendering, It has a amazing designer, is fully customizable and ships with Windows itself, there is no need for any dependencies, Microsoft is using it in Windows over and over again. Also, .NET Core 3.0 has support of WPF which means it can be used for cross-platform applications too!

    WPF :使用GPU渲染,它有一个了不起的设计器,可以完全自定义并且与Windows一起提供,不需要任何依赖关系,Microsoft在Windows中反复使用它。 此外,.NET Core 3.0还支持WPF,这意味着它也可以用于跨平台应用程序!

But the issue is it's only for .NET and C/C++ or Delphi developers cannot use it for their GUI if they want to. They should switch on C# language which has low security and less performance than C++ and if they want to mix it up with current solutions on the internet, it becomes unsafe, unstable and not cool at all!

但是问题在于,它仅适用于.NET和C / C ++,否则Delphi开发人员便无法在其GUI中使用它。 他们应该使用安全性低,性能不如C ++的C#语言,如果要将它们与Internet上的当前解决方案混合使用,它将变得不安全,不稳定并且一点也不酷!

But sometimes doing something complex is so much simpler than it looks! How? Let me quote from a KING:

但是有时候做一些复杂的事情比看起来简单得多! 怎么样? 让我引用国王的话:

Heisenberg:
海森堡:

Awh nothing special... it's just the Basics!

没什么特别的... 只是基础知识!

I always wanted to make this work!... Using C++ as my backend application and WPF/Winform for GUI and frontend because it's safe/fast in backend and pretty/easy-to-use in frontend.

我一直想做这个工作!...使用C ++作为我的后端应用程序,并使用WPF / Winform进行GUI和前端,因为它在后端安全/快速并且在前端漂亮/易于使用。

In this solution, you can use WPF/Winforms and QT and whatever else you want together.

在此解决方案中,可以一起使用WPF / Winforms和QT以及其他所需的工具。

现有方法 (Current Existing Methods)

Okay before we start, let's take a look at the current methods on the market and make a brief review...

好吧,在我们开始之前,让我们看一下市场上的当前方法并进行简短的回顾...

  • ActiveX: The first thing you always hear from embedding a WPF/Winform inside C++ ActiveX control but it's bad, really bad! Creating another platform for using two another platforms ... awh that's mixed up! and on top of everything, it's visible and accessible to everyone and they can use it too... an advice from your programmer friend... never use COM for UI...

    ActiveX :在W ++ / Winform嵌入C ++ ActiveX控件时,您总是会听到的第一件事,但这很糟糕,真的很糟糕! 创建另一个平台以使用另外两个平台...混在一起! 而且在所有内容之上,每个人都可以看到和访问它,他们也可以使用它...程序员朋友的建议...切勿将COM用于UI ...

  • CLRHosting: Second thing everyone finds on net is CLR Hosting Interface, yeah it's good and stable but not enough for mixing only UI and frontend, I prefer this to run a whole .NET app inside my native application.

    CLRHosting :每个人都在网上发现的第二件事是CLR Hosting Interface,是的,它既稳定又稳定,但不足以仅混合UI和前端,我更喜欢在本地应用程序中运行整个.NET应用程序。

  • C++/CLI: The last thing anyone wants to mess with. Needs invoking and lots of pain, also CLR/C++ assemblies are hard to integrate.

    C ++ / CLI :任何人都想弄的最后一件事。 需要调用和很多痛苦,CLR / C ++程序集也很难集成。

  • Noesis GUI: Another possible way, But eh ... needs implanting everything, blah blah ...

    Noesis GUI :另一种可能的方式,但是……需要植入所有东西,等等……

  • WPF Rendering Redirection: In this method, you run a hidden instance of WPF window and renders it in a texture2D, then you show it in a C++ window and redirect every received window messages to WPF hidden window. It works! but ... you know... we are here for a clean and professional way... :D

    WPF渲染重定向 :在此方法中,您将运行WPF窗口的隐藏实例并将其呈现在texture2D中,然后在C ++窗口中将其显示,并将接收到的所有窗口消息都重定向到WPF隐藏窗口。 有用! 但是...你知道吗...我们在这里是一种干净专业的方式...:D

我的基本方法 (My Pretty Basic Method)

This method I will teach you is a part of my big tutorial pack "Architecting Your Application Like A Boss". I will publish it part by part on CodeProject so ... take a seat and enjoy the show! :)

我将教您的这种方法是我的大型教程包 像Boss一样构建您的应用程序 ”的一部分。 我将在CodeProject上部分地发布它,以便……坐下来欣赏表演! :)

Steps we're going to follow are:

我们要遵循的步骤是:

  1. Creating a host window in C++

    在C ++中创建主机窗口
  2. Creating our WPF GUI library in C#

    在C#中创建WPF GUI库
  3. Using our library in C++ to create WPF GUI

    使用C ++库创建WPF GUI
  4. Exchanging functions with C# from C++ and reverse

    从C ++和C#与C#交换功能
  5. Creating callbacks and installing Windows hooks

    创建回调并安装Windows挂钩
  6. There's no step 6, it's pretty basic remember ? :D

    没有步骤6,这很基本吗? :D
In My Method ...
在我的方法中...

Only thing you need is a single handle...

您只需要一个手柄即可...

Okay, enough talk, let's do this! :)

好吧,足够多的谈话,让我们做到这一点! :)

创建主机窗口 (Creating a Host Window)

Open Visual Studio and create a C++ Console Application in x64, use this basic code as your main cpp file:

打开Visual Studio并在x64中创建一个C ++控制台应用程序 ,将此基本代码用作您的主要cpp文件:

 Dependencies
#include <iostream>
#include <Windows.h>

using namespace std;

 Global Objects

 Global Configs

 Our Application Entry Point
int main()
{
    cout << "C++ Main App Started..." << endl;

    /// We Code Here ...

    cout << "C++ Main App Finished." << endl;
    getchar();
}

Now let's create our unmanaged Host Window ...

现在让我们创建非托管主机窗口...

  1. Define global objects under Global Objects and define Configs in Global Configs:

    Global Objects下定义全局对象,并在 Global Configs定义 Global Configs

     Global Objects
    WNDCLASSEX HostWindowClass; /// Our Host Window Class Object
    MSG loop_message; /// Loop Message for Host Window
    HINSTANCE hInstance = GetModuleHandle(NULL); /// Application Image Base Address
    HWND cpphwin_hwnd; /// Host Window Handle
    HWND wpf_hwnd; /// WPF Wrapper Handle
    
     Global Configs
    const wchar_t cpphwinCN[] = L"CppMAppHostWinClass"; /// Host Window Class Name
    bool isHWindowRunning = false; /// Host Window Running State
    	
  2. Create window class using (After /// We Code Here ...):

    使用(在/// We Code Here .../// We Code Here ... )创建窗口类:

    /// Creating Icon Object From Resources, Don't forget to include resource.h!
    HICON app_icon = LoadIcon(GetModuleHandle(0),MAKEINTRESOURCE(IDI_APPICON));
    
    /// Defining Our Host Window Class
    HostWindowClass.cbSize = sizeof(WNDCLASSEX); HostWindowClass.lpfnWndProc = HostWindowProc;
    HostWindowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    HostWindowClass.cbClsExtra = 0; HostWindowClass.style = 0;
    HostWindowClass.cbWndExtra = 0;    HostWindowClass.hInstance = hInstance;
    HostWindowClass.hIcon = app_icon; HostWindowClass.hIconSm = app_icon;
    HostWindowClass.lpszClassName = cpphwinCN; HostWindowClass.lpszMenuName = NULL;
    
    	

    Why not setting 'hbrBackground'? If you choose a background for your native window, it will flick over WPF rendering, just don't set it up.

    为什么不设置“ hbrBackground”? 如果您为本机窗口选择背景,它将跳过WPF渲染,只是不进行设置。

  3. Add a callback to host window using:

    使用以下命令向主机窗口添加回调:

     Host Window Callback, NOTE :Define This Before Your Entrypoint Function
    LRESULT CALLBACK HostWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
    {
        switch (msg)
        {
        case WM_CLOSE:
            DestroyWindow(hwnd);
            break;
        case WM_DESTROY:
            isHWindowRunning = false;
            break;
        default:
            return DefWindowProc(hwnd, msg, wParam, lParam);
        }
        return 0;
    }
    	
  4. Register host window class using:

    使用以下方法注册主机窗口类:

     Register Window
    if (!RegisterClassEx(&HostWindowClass))
    {
      cout << "Error, Code :" << GetLastError() << endl;
      getchar(); return 0;
    }
    	
  5. Ok time to create the window, but hidden...

    是时候创建窗口了,但是隐藏了...

    /// Creating Unmanaged Host Window
    cpphwin_hwnd = CreateWindowEx(
      WS_EX_CLIENTEDGE,
      cpphwinCN,
      GetSTR_Res(APPDATA_HWINDOW_NAME),
      WS_THICKFRAME | WS_OVERLAPPEDWINDOW,
      CW_USEDEFAULT, CW_USEDEFAULT, 800, 500,
      NULL, NULL, hInstance, NULL);
    
    /// Check if How Window is valid
    if (cpphwin_hwnd == NULL)
    {
      cout << "Error, Code :" << GetLastError() << endl;
      getchar(); return 0;
    }
    	
  6. [Optimal] If you want to make your window fixed size, use:

    [最佳]如果要使窗口固定大小,请使用:

    /// Making Window Fixed Size
    ::SetWindowLong(cpphwin_hwnd, GWL_STYLE, 
        GetWindowLong(cpphwin_hwnd, GWL_STYLE) & ~WS_SIZEBOX);
    	

Here we go, now we have a native host window...

现在我们有了本地主机窗口...

配置和显示主机窗口 (Configuring and Showing Host Window)

Ok, I know most of you know all these things, but there's very small details in parameters you need to be careful about, that's why I'm writing this code for you. ;)

好的,我知道你们中的大多数人都知道所有这些事情,但是您需要注意的参数中有非常小的细节,这就是为什么我为您编写此代码。 ;)

  1. Center your host window using:

    使用以下命令居中托管窗口:

    /// Centering Host Window
    RECT window_r; RECT desktop_r;
    GetWindowRect(cpphwin_hwnd, &window_r); GetWindowRect(GetDesktopWindow(), &desktop_r);
    int xPos = (desktop_r.right - (window_r.right - window_r.left)) / 2;
    int yPos = (desktop_r.bottom - (window_r.bottom - window_r.top)) / 2;
    
    /// Set Window Position
    ::SetWindowPos(cpphwin_hwnd, 0, xPos, yPos, 0, 0, SWP_NOZORDER | SWP_NOSIZE);
    	
  2. And finally, display the window using:

    最后,使用以下命令显示窗口:

    /// Display Window
    ShowWindow(cpphwin_hwnd, SW_SHOW);
    UpdateWindow(cpphwin_hwnd);
    BringWindowToTop(cpphwin_hwnd);
    isHWindowRunning = true;
    	
  3. Add message loop to avoid application freezing using:

    添加消息循环以避免使用以下命令冻结应用程序:

    /// Adding Message Loop
    while (GetMessage(&loop_message, NULL, 0, 0) > 0 && isHWindowRunning)
    {
     TranslateMessage(&loop_message);
     DispatchMessage(&loop_message);
    }
    	

Image 1

主要非托管应用 (Main Unmanaged Application)

Ok, time to develop your C++ application functionality, interfaces, etc. In this tutorial, I created a simple C++ application which compresses/decompresses files using LZ4 algorithm.

好的,是时候开发C ++应用程序功能,接口等了。在本教程中,我创建了一个简单的C ++应用程序,该应用程序使用LZ4算法压缩/解压缩文件。

Of course! .NET can do LZ4 compression just as perfect as C++ but this is an example for using a native library (lz4 library) and the point is using WPF just as GUI, your native application can be anything... there's no limitation.

当然! .NET可以像C ++一样完美地进行LZ4压缩,但这是使用本机库( lz4库 )的一个示例,关键是使用WPF作为GUI,您的本机应用程序可以是任何东西...没有限制。

Also at the end of the article, I tell you how you can use this method on Plugins and SDKs for which you don't have full access to entire parts.

同样在本文的结尾,我告诉您如何在无法完全访问整个部分的插件和SDK上使用此方法。

Here's the code of Main app but it's not a part of this tutorial, so I will not explain it... you can use anything you want. :)

这是Main应用程序的代码,但这不是本教程的一部分,因此我不会对其进行解释...您可以使用任何想要的东西。 :)

C ++ LZ4压缩器/解压缩器应用 (C++ LZ4 Compressor/Decompressor Application)

 Main App Codes
#pragma region Main App Codes

/// IncludingLZ4 Library -> https://github.com/lz4/lz4
#include "SDK\\lz4.h"
#pragma comment(lib, "SDK\\liblz4_static_vc2019.lib")
#include <vector>
#include <fstream>
ofstream file;

/// Abstract Vector Data
using buffer = vector<char>;

/// Lz4 Methods
BOOL lz4_compress(const buffer& in, buffer& out)
{
    auto rv = LZ4_compress_default(in.data(), out.data(), in.size(), out.size());
    if (rv < 1) { return FALSE; }
    else { out.resize(rv); return TRUE;}
}
BOOL lz4_decompress(const buffer& in, buffer& out)
{
    auto rv = LZ4_decompress_safe(in.data(), out.data(), in.size(), out.size());
    if (rv < 1) { return FALSE; }
    else { out.resize(rv); return TRUE; }
}

/// Read File
std::vector<char> readFile(const char* filename)
{
    std::basic_ifstream<char> file(filename, std::ios::binary);
    return std::vector<char>((std::istreambuf_iterator<char>(file)),
                              std::istreambuf_iterator<char>());
}

/// MainApp API Functions
BOOL LZ4_Compress_File(char* filename) {
    buffer org_filedata = readFile(filename);
    if(org_filedata.size() == 0){ return FALSE; }
    const size_t max_dst_size = LZ4_compressBound(org_filedata.size());
    vector<char> compressed_data(max_dst_size);
    BOOL compress_data_with_lz4 = lz4_compress(org_filedata, compressed_data);
    if (!compress_data_with_lz4) { return FALSE; }
    string out_put_file_name = filename + string("_.lz4");
    file.open(out_put_file_name, ios::binary | ios::out);
    file.write((char*)compressed_data.data(), compressed_data.size());
    file.close();
    SecureZeroMemory(org_filedata.data(), org_filedata.size());
    return TRUE;
}

BOOL LZ4_Decompress_File(char* filename,long originalSize) {
    vector<char> decompressed_data;
    decompressed_data.resize(originalSize);
    buffer org_filedata = readFile(filename);
    if (org_filedata.size() == 0) { return FALSE; }
    BOOL decompress_data_with_lz4 = lz4_decompress(org_filedata, decompressed_data);
    string out_put_file_name(filename);
    out_put_file_name = out_put_file_name.replace
                        (out_put_file_name.find("_.lz4"), sizeof("_.lz4") - 1, "");
    file.open(out_put_file_name, ios::binary | ios::out);
    file.write((char*)decompressed_data.data(), decompressed_data.size());
    file.close();
    return TRUE;
}
#pragma endregion

Quick Note: Original size can be appended in the beginning or end of your compressed file.

快速说明 :原始大小可以添加到压缩文件的开头或结尾。

创建WPF / Winform用户界面 (Creating WPF/Winform User Interface)

Okay, Now it's time to create our WPF or Winform user interface, Create a x64 C# Library with your desired platform, I use WPF because it's what I promise in this tutorial, but you can use Winform as well.

好的,现在是时候创建我们的WPF或Winform用户界面了,使用所需的平台创建一个x64 C#库 ,我使用WPF,因为这是本教程中所保证的,但是您也可以使用Winform。

The main key of this method is DllExport from your C# library. We run a C# application right inside the heart of a C++ unmanaged application using DllExports and everything will be done by the Operating System, there is no need to use CLRHosting and ActiveX.

此方法的主键是C#库中的DllExport 。 我们使用DllExport在C ++非托管应用程序的内部运行C#应用程序,并且一切将由操作系统完成,无需使用CLRHosting和ActiveX。

For exporting your functions in a managed DLL, you need DllExport nuget package which you can grab from here.

要在托管DLL中导出函数,您需要DllExport nuget包,您可以从此处获取它。

Create a WPF/Winform window and design your layouts, skins and stuff.

创建一个WPF / Winform窗口并设计布局,外观和内容。

这是我使用WpfPlus库为我的简单压缩器设计的。 (Here's what I designed for my simple compressor with WpfPlus library.)

Image 2

As you can see, there are some buttons and a list for storing files and a log view for data debug, etc.

如您所见,有一些按钮和一个用于存储文件的列表以及一个用于数据调试的日志视图等。

Quick Note: Before building your GUI library, make sure you set your Window Style to None and Unresizable.

快速说明 :在构建GUI库之前,请确保将Window Style设置为None和Unresizable

连接的魔力 (Magic of Connection)

Well, it's time to do some magic (science) to connect our Unmanaged world to Managed one.

好了,该做些魔术(科学)将我们的非托管世界与托管世界联系起来的时候了。

For creating a clean and good communication between our C++ and C# app, we're going to use function pointers and delegating.

为了在我们的C ++和C#应用程序之间创建干净而良好的通信,我们将使用函数指针和委托。

We have two API functions:

我们有两个API函数:

  1. LZ4_Compress_File with boolean output and one filename input

    LZ4_Compress_File具有布尔输出和一个文件名输入

  2. LZ4_Decompress_File with boolean output and two filename and size input

    LZ4_Decompress_File具有布尔输出和两个文件名和大小输入

Now we have to get pointers of our functions, add this function pointers typedefs in your global objects:

现在,我们必须获取函数的指针,并在全局对象中添加此函数指针typedef

typedef void (*LZ4_Compress_File_Ptr)(void);
typedef void (*LZ4_Decompress_File_Ptr)(void);

  1. In C# project, add a class and name it UIBridge and add this function into it based on your platform:

    在C#项目中,添加一个类并将其命名为UIBridge ,然后根据您的平台将以下函数添加到其中:

    For WPF:

    对于WPF:

    using System;
    using DllExportLib; /// This depends on your using library
    using System.Windows.Interop;
    
    namespace ManagedUIKitWPF
    {
        class UIBridge
        {
            public static MainView mainview_ui;
            [DllExport]
            static public IntPtr CreateUserInterface(IntPtr api_1_ptr, IntPtr api_2_ptr)
            {
                mainview_ui = new MainView(api_1_ptr, api_2_ptr)
                {
                    Opacity = 0,
                    Width = 0,
                    Height = 0
                };
                mainview_ui.Show();
                return new WindowInteropHelper(mainview_ui).Handle;
            }
    
            [DllExport]
            static public void DisplayUserInterface()
            {
                mainview_ui.Opacity = 1;
            }
    
            [DllExport]
            static public void DestroyUserInterface()
            {
                mainview_ui.Close();
            }
        }
    }
    
    	

    For Winform:

    对于Winform:

    using System;
    using DllExportLib; /// This depends on your using library
    using System.Windows.Forms;
    
    namespace ManagedUIKitWPF
    {
        class UIBridge
        {
            public static MainView mainview_ui;
            [DllExport]
            static public IntPtr CreateUserInterface(IntPtr api_1_ptr, IntPtr api_2_ptr)
            {
                mainview_ui = new MainView(api_1_ptr, api_2_ptr)
                mainview_ui.Opacity = 0f;
                mainview_ui.Show();
                return mainview_ui.Handle;
            }
    
            [DllExport]
            static public void DisplayUserInterface()
            {
                mainview_ui.Opacity = 1.0f;
            }
    
            [DllExport]
            static public void DestroyUserInterface()
            {
                mainview_ui.Close();
                mainview_ui.Dispose();
            }
        }
    }
    
    	

    Why opacity is 0 in window creation? It's simple, because of flicking free window creation, if you don't use 0 opacity, it flicks randomly when you're trying to show it, we manually display the Window/Form when our host will be ready...

    为什么在窗口创建中不透明度为0? 很简单,因为可以自由创建自由窗口,如果您不使用0不透明度,则在尝试显示不透明窗口时会随机滑动,我们会在主机准备就绪时手动显示窗口/窗体...

    What to do if I had too many functions? Just try to pass an IntPtr list or array or even simpler, a string or json data, whatever you want is possible, just find your desired way!

    如果我的功能太多,该怎么办? 只要尝试传递IntPtr列表或数组,甚至更简单的string或json数据,就可以找到想要的方式!

    Also, for printing log data from your native app to .NET GUI, use the same method to add PrintLog(string log_str) export and assign them in C++.

    另外,为了将日志数据从本地应用程序打印到.NET GUI,请使用相同的方法添加PrintLog(string log_str)导出并在C ++中进行分配。

  2. Okay, now go in your window backend code and add delegates of your functions:

    好的,现在进入您的窗口后端代码并添加函数的委托:

    using System;
    using System.Windows;
    using System.Runtime.InteropServices; /// We need this...
    
    namespace ManagedUIKitWPF
    {
        public partial class MainView : Window
        {
             API Delegates
            delegate bool LZ4_Compress_File_Ptr(string filename);
            delegate bool LZ4_Decompress_File_Ptr(string filename,long filesize);
    
             Ported Functions
            LZ4_Compress_File_Ptr LZ4_Compress_File;
            LZ4_Decompress_File_Ptr LZ4_Decompress_File;
    
            public MainView(IntPtr api_1_ptr, IntPtr api_2_ptr)
            {
                InitializeComponent();
            }
        }
    }
    	
  3. Now we need to get functions in C# using pointers we passed from C++. We have something amazing in .NET known as Marshal.GetDelegateForFunctionPointer which does exactly what we need.

    现在,我们需要使用从C ++传递的指针来获取C#中的函数。 在.NET中,我们有一个惊人的功能,称为Marshal.GetDelegateForFunctionPointer ,它确实Marshal.GetDelegateForFunctionPointer我们的需求。

    Add this code to your window code right after InitializeComponent():

    InitializeComponent() 之后立即将此代码添加到您的窗口代码中:

    InitializeComponent();
    
     Recovering Native Functions
    LZ4_Compress_File = (LZ4_Compress_File_Ptr)Marshal.GetDelegateForFunctionPointer
                        (api_1_ptr, typeof(LZ4_Compress_File_Ptr));
    LZ4_Decompress_File = (LZ4_Decompress_File_Ptr)Marshal.GetDelegateForFunctionPointer
                          (api_2_ptr, typeof(LZ4_Decompress_File_Ptr));
    	
  4. And finally, it's time to pass the functions from C++. First of all, we need to Load our .NET GUI library, define these objects and values in your Global Objects:

    最后,是时候从C ++传递函数了。 首先,我们需要加载.NET GUI库,在全局对象中定义这些对象和值:

    typedef HWND(*CreateUserInterfaceFunc)(LZ4_Compress_File_Ptr, LZ4_Decompress_File_Ptr);
    CreateUserInterfaceFunc CreateUserInterface;
    typedef void(*DisplayUserInterfaceFunc)(void);
    DisplayUserInterfaceFunc DisplayUserInterface;
    typedef void(*DestroyUserInterfaceFunc)(void);
    DestroyUserInterfaceFunc DestroyUserInterface;
    
    	

    Quick Note: You can use DisplayUserInterfaceFunc type for both DisplayUserInterface and DestroyUserInterface because they're using the same type, but you know ...

    快速说明DisplayUserInterfaceDestroyUserInterface都可以使用DisplayUserInterfaceFunc类型,因为它们使用的是同一类型,但是您知道...

  5. Use LoadLibrary and GetProcAddress to Load your .NET GUI library and functions:

    使用LoadLibraryGetProcAddress加载.NET GUI库和函数:

    /// Loading dotNet UI Library
    HMODULE dotNetGUILibrary = LoadLibrary(L"ManagedUIKitWPF.dll");
    CreateUserInterface = (CreateUserInterfaceFunc)GetProcAddress
                          (dotNetGUILibrary,"CreateUserInterface");
    DisplayUserInterface = (DisplayUserInterfaceFunc)GetProcAddress
                           (dotNetGUILibrary, "DisplayUserInterface");
    DestroyUserInterface = (DestroyUserInterfaceFunc)GetProcAddress
                           (dotNetGUILibrary, "DestroyUserInterface");
    
    	
    <在ShowWindow(cpphwin_hwnd,SW_SHOW)之前添加此代码; > (< Add this code before ShowWindow(cpphwin_hwnd, SW_SHOW); >)
  6. Use CreateUserInterface just before ShowWindow and pass function pointers into dotNet Library...

    ShowWindow之前使用CreateUserInterface并将函数指针传递到dotNet库...

    /// Creating .Net GUI
    wpf_hwnd = CreateUserInterface(
        (LZ4_Compress_File_Ptr)&LZ4_Compress_File,
        (LZ4_Decompress_File_Ptr)&LZ4_Decompress_File);
    	

Here we go... now build your application and library and give it a try. If you get error or crash, please review your code with more details...

我们开始...现在构建您的应用程序和库并尝试一下。 如果您遇到错误或崩溃,请查看代码以获取更多详细信息...

IMPORTANT NOTE :
重要的提示 :

If you received any error about STA thread, just add CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); in your code before calling CreateUserInterface function...

如果收到有关STA线程的任何错误,只需添加CoInitializeEx(NULL, COINIT_APARTMENTTHREADED); 在代码中调用CreateUserInterface函数之前...

WARNING :
警告 :

If you're using WPF UI for your application don't use Double Buffering (WS_EX_COMPOSITED) feature on your c++ host window, WPF uses DirectX renderer and has already double buffering, If you use it in your host window it will break WPF rendering.

如果您在应用程序中使用WPF UI,则不要在c ++宿主窗口上使用双重缓冲(WS_EX_COMPOSITED)功能,WPF使用DirectX渲染器并且已经具有双重缓冲,如果在宿主窗口中使用它会破坏WPF渲染。

Good job! Now your unmanaged app is connected to your managed GUI under the skin. It's time to connect them in frontend too!

做得好! 现在,您的非托管应用已连接到皮肤下的托管GUI。 现在也该在前端连接它们了!

在C ++环境中显示GUI (Displaying GUI in C++ Environment)

Remember that I riped off the basic part from Heisenberg? Well... it's on ...

还记得我从海森堡学到的基本知识吗? 好吧...开...

A)在主机窗口中显示WPF窗口作为自定义控件(子级) (A) Displaying WPF Window as a Custom Control (child) in our Host Window)

Here's the magic code to turn your WPF window to a child and host it perfectly in your native window:

这是将您的WPF窗口变成一个孩子并将其完美地托管在本机窗口中的神奇代码:

RECT hwin_rect; /// Add this in global objects

/// Check if WPF Window is valid
if (wpf_hwnd != nullptr) {

        /// Disable Host Window Updates & Draws
        SendMessage(cpphwin_hwnd, WM_SETREDRAW, FALSE, 0);

        /// Disable Host Window Double Buffering
        long dwExStyle = GetWindowLong(cpphwin_hwnd, GWL_EXSTYLE);
        dwExStyle &= ~WS_EX_COMPOSITED;
        SetWindowLong(cpphwin_hwnd, GWL_EXSTYLE, dwExStyle);

        /// Set WPF Window to a Child Control
        SetWindowLong(wpf_hwnd, GWL_STYLE, WS_CHILD);

        /// Get your host client area rect
        GetClientRect(cpphwin_hwnd, &hwin_rect);

        /// Set WPF Control Order, Size and Position
        MoveWindow(wpf_hwnd, 0, 0, hwin_rect.right - hwin_rect.left, 
                   hwin_rect.bottom - hwin_rect.top, TRUE);
        SetWindowPos(wpf_hwnd, HWND_TOP, 0, 0, hwin_rect.right - hwin_rect.left, 
                     hwin_rect.bottom - hwin_rect.top, SWP_NOMOVE);

        /// Set WPF as A Child to Host Window...
        SetParent(wpf_hwnd, cpphwin_hwnd);

        /// Skadoosh!
        ShowWindow(wpf_hwnd,SW_RESTORE);

        /// Display WPF Control by resetting its Opacity
        DisplayUserInterface();
}

此代码应介于CreateUserInterfaceShowWindow之间 (This code should be between CreateUserInterface and ShowWindow)

B)后期控制更新和消息传递 (B) Post Control Updates And Messaging)

If the user resizes the host window, our control needs to be resized too, go back in HostWindowProc callback and add resize event and close event into it:

如果用户调整了宿主窗口的大小,则我们的控件也需要调整大小,返回HostWindowProc回调并在HostWindowProc添加resize事件和close事件:

 Host Window Callback
LRESULT CALLBACK HostWindowProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CLOSE:
        DestroyUserInterface();  Destroy WPF Control before Destroying Host Window
        DestroyWindow(hwnd);
        break;
    case WM_DESTROY:
        isHWindowRunning = false;
        break;
    case WM_SIZE:  Resize WPF Control on Host Window Resizing
        if (wpf_hwnd!=nullptr) {
            GetClientRect(cpphwin_hwnd, &hwin_rect);
            MoveWindow(wpf_hwnd, 0, 0, hwin_rect.right - hwin_rect.left, 
                       hwin_rect.bottom - hwin_rect.top, TRUE);
        }
        break;
    default:
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }
    return 0;
}

Okay. :) Time to build your application and library ... and here you go ... you have a smooth and flicker free WPF UI inside your C++ application.

好的。 :)是时候构建应用程序和库了……接下来,您可以在C ++应用程序中拥有一个流畅而无闪烁的WPF UI。

问题1 :应用程序运行但冻结 (Issue 1: Application Runs But It Freezes)

If you launched your EXE and it simply went frozen, don't panic... It's totally natural!

如果您启动了EXE而只是冻结了,请不要慌张…… 这是完全自然的!

In some cases like Console Apps or Standalone apps in which threading is running as default, WPF UI runs in the same thread and its message loop breaks the main application thread.

在某些情况下,例如控制台应用程序或独立应用程序 (其中的线程默认运行),WPF UI在同一线程中运行,并且其消息循环中断主应用程序线程。

In this case, you should Use Multi Threading and create a separate thread for your GUI which makes your application work in two different threads, Backend Thread and Frontend Thread.

在这种情况下,您应该使用多线程并为GUI创建一个单独的线程,这使您的应用程序可以在两个不同的线程Backend ThreadFrontend Thread中工作

It goes even more safer and standard as a complex application. Okay, go in your Managed Bridge and change the code to this:

作为复杂的应用程序,它变得更加安全和标准。 好的,进入您的Managed Bridge并将代码更改为此:

using System.Threading;  Add this in your file

class UIBridge
{
 public static MainView mainview_ui;
 public static Thread gui_thread;
 public static IntPtr mainview_handle = IntPtr.Zero;

 [DllExport]
 static public IntPtr CreateUserInterface
        (IntPtr api_1_ptr, IntPtr api_2_ptr) /// Multi-Threaded Version
        {
            gui_thread = new Thread(() =>
            {
                mainview_ui = new MainView(api_1_ptr, api_2_ptr)
                {Opacity = 0,Width = 0,Height = 0};
                mainview_ui.Show();
                mainview_handle = new WindowInteropHelper(mainview_ui).Handle;
                System.Windows.Threading.Dispatcher.Run();
            });
            gui_thread.SetApartmentState(ApartmentState.STA); /// STA Thread Initialization
            gui_thread.Start();

            while (mainview_handle == IntPtr.Zero) { }
            return mainview_handle;
        }

 [DllExport]
 static public void DisplayUserInterface() /// Multi-Threaded Version
        {
            try
            {
                mainview_ui.Opacity = 1;
            }
            catch /// Can't Access to UI Thread, So Dispatching
            {
                mainview_ui.Dispatcher.BeginInvoke((Action)(() => {
                    mainview_ui.Opacity = 1;
                }));
            }
        }

 [DllExport]
 static public void DestroyUserInterface() /// Multi-Threaded Version
        {
        try {
             mainview_ui.Close();
            }
            catch /// Can't Access to UI Thread, So Dispatching
            {
                mainview_ui.Dispatcher.BeginInvoke((Action)(()=> {
                    mainview_ui.Close();
                }));
            }
         }
}

Now build and try again... It's working now! :)

现在构建并重试...它正在工作! :)

问题2 :我无权创建主机窗口 (Issue 2: I Don't Have Access to Host Window Creation)

Ok folks, there are some situations when we don't have full access to host window creation, like when we want to use our WPF UI in some native plugin for applications like 3ds Max, Photoshop, QT Apps, etc.

好的,有些情况下,我们无法完全访问主机窗口的创建,例如,当我们想在某些本机插件中使用WPF UI来安装3ds Max,Photoshop,QT Apps等应用程序时。

As a plugin, SDKs lets you create Child Windows, Panels, Rollups, Rollouts, Custom Controls and returns a handle to it, but underneath all of this guys are Window!

作为一个插件,SDK允许您创建子Windows,面板,汇总,展示,自定义控件并返回其句柄,但在所有这些控件的下面都是Window!

In this case, there are three issues:

在这种情况下,存在三个问题:

  • We don't have access to the host window WndProc Callback

    我们无权访问主机窗口WndProc回调

  • We don't have access to the host window Handle Pointer

    我们无权访问主机窗口句柄指针

  • We don't know the structure of host window in target application

    我们不知道目标应用程序中宿主窗口的结构
1)如何处理WndProc控件不足 (1) How to Deal With Lack of WndProc Control)

In this case, you need to set a Window Hook on your host window to catch its messages, this method has security side effects if you try to use it on another process window because this is what keyloggers do, but in our case it's a plugin and it's running inside the same process so... there's no problem and any issue to doing this and Windows defender doesn't bust your ass on this. :)

在这种情况下,您需要在主机窗口上设置一个“ 窗口挂钩 ”以捕获其消息,如果您尝试在另一个进程窗口上使用它,则此方法具有安全副作用,因为这是键盘记录程序所做的,但在我们的情况下,它是一个插件而且它在同一进程中运行,因此...没问题,这样做没有任何问题,Windows Defender不会破坏您的工作。 :)

To setup a windows hook on your host window, you only need its handle and most of the SDKs return the handle of host window/panel and we use this handle via SetWindowSubclass to set an alternative message capture callback to our host window/panel.

要在主机窗口上设置窗口挂钩,您只需要其句柄,大多数SDK会返回主机窗口/面板的句柄,我们通过SetWindowSubclass使用此句柄为主机窗口/面板设置备用消息捕获回调。

Create the same HostWindowProc in your plugin code and add this code just after you showed your window/panel:

在您的插件代码中创建相同的HostWindowProc ,并在显示窗口/面板后添加以下代码:

SetWindowSubclass(<Window/Panel Handle>, &HostWindowProc, <Subclass UID example '6663'>, 0);

And don't forget to apply two changes in your WndProc:

并且不要忘记在WndProc应用两个更改:

  1. Add RemoveWindowSubclass(hWnd, &HostWindowProc, 1); in WM_DESTROY

    添加RemoveWindowSubclass(hWnd, &HostWindowProc, 1);WM_DESTROY

  2. Change your HostWindowProc CALLBACK parameters to:

    将您的HostWindowProc CALLBACK参数更改为:

LRESULT CALLBACK HostWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, 
                                LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)

Done! Now you can handle resizing and other message events on any kind of window and panel.

做完了! 现在,您可以在任何类型的窗口和面板上处理大小调整和其他消息事件。

2)如何处理缺失的句柄 (2) How to Deal With Missing Handles)

In this case, you need to query your target application windows and find the created host window/panel to be able to do the rest. If SDK doesn't give you the handle, it lets you provide a title for it, you just need to query and find it by its title.

在这种情况下,您需要查询目标应用程序窗口并找到创建的主机窗口/面板以完成其余工作。 如果SDK没有给您提供句柄,则可以为它提供标题,您只需要按其标题进行查询和查找即可。

To find all child windows in your process, you need to use EnumChildWindows and GetWindowText or GetClassName:

要查找过程中的所有子窗口,您需要使用EnumChildWindowsGetWindowTextGetClassName

 Global Objects
HWND host_window_handle;

 Window Query to Find Host Window/Panel
BOOL CALLBACK HostWindowQueryCB(HWND hwnd, LPARAM lParam) 
{
    TCHAR* target_class_name = L"Window/Panel Class Name";
    TCHAR* target_text = L"Window/Panel Title Text";

     Get Class Name Of Window
    TCHAR wnd_class_name[MAX_PATH];
    GetClassName(hwnd, wnd_class_name, _countof(wnd_class_name));
    
     Get Title/Text Of Window
    TCHAR wnd_title_text[MAX_PATH]; 
    GetWindowText(hwnd,wnd_title_text,MAX_PATH);
    
     Compare Window Text
    if (_tcscmp(wnd_title_text, target_text) == 0) { 
        host_window_handle = hwnd;
        return FALSE; /// Found it 
    }

     Compare Window Class Name
    if (_tcscmp(wnd_class_name, target_class_name ) == 0) {
        host_window_handle = hwnd;
        return FALSE; /// Found it
    }

    return TRUE;
}

And use this function to start the query ...

并使用此功能启动查询...

 Query Child Windows to find target Host
EnumChildWindows(<Your Application Main Window Handle>, HostWindowQueryCB, 0);

Optimization Tip: You can use GetWindowThreadProcessId and GetCurrentProcessId in an IF_ELSE to check if window belongs to your current process or not, if yes, then do the rest getting & comparing and stuff.

优化技巧 :可以在IF_ELSE使用GetWindowThreadProcessIdGetCurrentProcessId来检查window是否属于您当前的进程,如果是,则进行其余的获取与比较和填充。

3)如何处理目标应用程序的未知结构 (3) How to Deal Unknown Structures of Target Application)

It's really simple! You can use a great and lightweight tool from Microsoft "Spy++". You can find this little guy in your Visual Studio folder Microsoft Visual Studio\20XX\Enterprise\CommonX\Tools.

真的很简单! 您可以使用Microsoft“ Spy ++”的强大轻量级工具。 您可以在Visual Studio文件夹Microsoft Visual Studio \ 20XX \ Enterprise \ CommonX \ Tools中找到这个小家伙。

编程前端用户界面 (Programming Frontend UI)

Congratulations! You could make your way to the final stage... It's time to develop our managed frontend and make everything work just fine...

恭喜你! 您可以进入最后阶段...是时候开发我们的托管前端并使一切正常工作了...

In Frontend development, we have two different strategies:

在前端开发中,我们有两种不同的策略:

  • Do everything in backend and just use the C# to calling functions, displaying data or drawing custom things.

    在后端执行所有操作,仅使用C#调用函数,显示数据或绘制自定义内容。
  • Use C# more than just a UI and gain more power in development!

    使用C#不仅仅是UI,还可以在开发中获得更多功能!

Example: In my compressor, I can use buttons to add files in backend C++ list and do everything behind and just display data in WPF list and use buttons to call compress/decompress functions or I can use C# to manage files, compressing and decompressing processes which saves me a lot of time!

示例在我的压缩器中,我可以使用按钮在后端C ++列表中添加文件,并在后面进行所有操作,而仅在WPF列表中显示数据,并使用按钮来调用压缩/解压缩功能,或者可以使用C#来管理文件,压缩和解压缩过程这节省了我很多时间!

It completely depends on your decision and we're living in a free world, right? ;)

这完全取决于您的决定,我们生活在一个自由的世界中,对吗? ;)

在托管应用中使用本机API就像 (Using Your Native API in Your Managed App Is Like)
/// Compression UI Function
private void Compress_Button_Click(object sender, RoutedEventArgs e)
{
     string file_path = get_selected_file();
     bool compression_process = LZ4_Compress_File(file_path); /// Native API

     if (compression_process)
     PrintLog($"File '{Path.GetFileName(file_path)}' has been compressed successfully!");
     else
     PrintLog($"File '{Path.GetFileName(file_path)}' compression failed.");
}

After finishing your UI programming, build your application and library. Run your C++ application.

完成UI编程后,构建您的应用程序和库。 运行您的C ++应用程序。

Now you're watching the magic of science... :)

现在您正在观看科学的魔力... :)

Image 3
要观看平滑视频, 请单击此处下载“ CppWPFUI.mp4”(350 KB) (To watch a video of smoothness, click here to download 'CppWPFUI.mp4' (350 KB))

最佳解决方案 (Optimal Solutions)

在C ++应用程序中打包GUI (Packing Your GUI in C++ Application)

You can use an amazing free application "Enigma Virtual Box" which you can grab from here on the official site.

您可以使用一个很棒的免费应用程序“ Enigma Virtual Box” ,您可以在这里从官方网站上获取它。

Select your C++ EXE and add your WPF GUI library .dll file in root of virtual box, then build your EXE to a single one and use compression in settings.

选择您的C ++ EXE,并将您的WPF GUI库.dll文件添加到虚拟框的根目录中,然后将您的EXE构建为一个,并在设置中使用压缩功能。

Now you have a clean, lightweight and single file C++ application with a beautiful, smooth, flicker free WPF user interface.

现在,您将获得一个干净,轻巧和单一文件的C ++应用程序,它具有漂亮,流畅,无闪烁的WPF用户界面。

改善本机功能的安全性 (Improving Security of Your Native Functions)

To secure your C++ API pointers from unofficial assemblies, you can use MD5 or SHA hashing technique to check if requester has been modified or not.

为了保护非官方程序集的C ++ API指针,可以使用MD5或SHA哈希技术检查请求者是否已被修改。

Let me introduce you to an amazing library Digest++ which gives you a perfect hashing methods and it's header-only!

让我向您介绍一个令人惊叹的Digest ++库,它为您提供了一种完美的哈希方法,并且仅标头!

To secure your native functions:

为了保护您的本机功能:

  1. After building the final version of GUI library, DLL get its hash in MD5, SHA512, SHA1, etc.

    构建最终版本的GUI库后,DLL在MD5,SHA512,SHA1等中获取其哈希值。

  2. Write it down and store the hash as XOR Obfuscated String (Or AES256) in your main C++ application.

    记下它,并将哈希作为XOR模糊字符串 (或AES256)存储在您的主C ++应用程序中。

  3. Before using LoadLibrary function to load your GUI library DLL, get its hash again and compare it to pre-calculated hash you generated before and if it matched, continue the processing...

    在使用LoadLibrary函数加载GUI库DLL之前,请再次获取其哈希值并将其与您之前生成的预先计算的哈希值进行比较,如果匹配,请继续进行处理...

转换为Windows子系统 (Converting to Windows SubSystem)

After you're done with debugging, you can turn your console application to Windows SubSystem following:

完成调试后,可以按照以下步骤将控制台应用程序转换为Windows SubSystem:

  1. replace int main() with int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)

    将int main()替换为int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd)

  2. Go in C++ Project Settings>Linker>System and change SubSystem from Console (/SUBSYSTEM:CONSOLE) to Windows (/SUBSYSTEM:WINDOWS)

    进入C ++项目设置>链接器>系统 ,将SubSystemConsole (/SUBSYSTEM:CONSOLE)更改为Windows (/SUBSYSTEM:WINDOWS)

在QT Framework中使用此方法 (Using this Method in QT Framework)

Everything in QT is like Native WinAPI, you just need to follow these tips: QT中的所有内容都类似于本机WinAPI,您只需遵循以下提示:
  • Use this function to get Native Handle of your QT Window/Widget:

    使用此函数来获取您的QT窗​​口/小部件的本机句柄:
HWND GetQTNativeHwnd = (HWND)MyQTWindow->winId();

  • Instead of using Rect and GetClientArea, use:

    代替使用RectGetClientArea ,请使用:

QSize QTHostSize = MyQTWindow->size();
int win_w = QTHostSize.width();
int win_h = QTHostSize.height();

MoveWindow(wpf_hwnd, 0, 0, win_w, win_h, TRUE);
SetWindowPos(wpf_hwnd, HWND_TOP, 0, 0, win_w, win_h, SWP_NOMOVE);

源代码 (Source Code)

You can download the full source code and final binary from the links below:

您可以从以下链接下载完整的源代码和最终二进制文件:

下一步是什么? (What's Next?)

In the next section of my article series, I will teach you how to directly use all of your C++ Memory and Values inside C# managed application so you don't have to build new values, pass them or allocate more memory.

在我的文章系列的下一部分中,我将教您如何在C#托管应用程序中直接使用所有C ++内存和值,因此您不必构建新值,传递它们或分配更多的内存。

This technique is a great help when you're dealing with huge amount of data!

当您处理大量数据时,此技术是非常有用的帮助!

I hope this tutorial has been helpful to you. :)

希望本教程对您有所帮助。 :)

Also, feel free to follow my latest application under development in its official discord server.

另外,请随时在我的官方不和谐服务器中关注我正在开发的最新应用程序。

Happy New Year!

新年快乐!

翻译自: https://www.codeproject.com/Articles/5253279/Create-An-Awesome-WPF-UI-for-Your-Cplusplus-QT-App

vs +qt 怎么创建ui

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值