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

在 Linux 上使用 C# 运行 NoesisGUI(一个 WPF 竞争框架)的入门指南

starIconstarIconstarIconstarIconstarIcon

5.00/5 (6投票s)

2018 年 3 月 15 日

CPOL

6分钟阅读

viewsIcon

25385

downloadIcon

269

本文将介绍如何在 Linux 上使用 MonoDevelop 和 C# 运行功能强大、特性丰富的 NoesisGUI(可能是 WPF 的最佳竞争者)的 -IntegrationSample-。

下载 MonoDevelop on Linux x86_64 的示例项目(源代码和预编译二进制文件)

引言

我对 C# 的富 GUI 框架非常感兴趣(请参阅我关于 XlibAthenaMotifFWFRoma Widget Set 的文章)。然而,自从 WPF 问世以来,很明显传统的控件集已经过时了。

要实现与 WPF 相当的功能,最强大且跨平台的 are Pango+Cairo,甚至更强大的 OpenGL。但是,基于 OpenGL 创建一个功能丰富的 GUI 框架是一项非常艰巨的任务(请参阅我关于 入门OpenGL/OpenTK 的文章)。

最近,我偶然发现了 NoesisGUI,它也提供免费的 INDIE 版本(您只需保证年收入低于 100,000 欧元)。我立刻被它迷住了。

背景

NoesisGUI 被宣传为“基于 XAML 的跨平台游戏 UI 中间件”,但它也可以拥有严肃桌面应用程序的外观和感觉。不幸的是,下载的 NoesisGUI-ManagedSDK-2.0.2f1.zip 中的任何示例,或者 NoesisGUI 主页 上的示例,都未针对 Linux 进行准备。

本文提供了一个可在 Linux 上直接运行的 C# 项目,该项目使用 MonoDevelop 5.10 在 openSuse Leap 42.1 64位上创建。但我相信,让这个项目在稍旧或稍新的 Linux 版本以及不同发行版上运行也会很容易。

该项目提供了针对 Linux 及其依赖项的 NoesisGUI IntegrationSample 重构版本:

当使用 NoesisStyle.xaml 时,NoesisGUI IntegrationSample 的外观如下:

或者,安装包中包含 SimpleStyle.xaml 样式,外观如下:

以及 WindowsStyle.xaml 样式,外观如下:

提示:不要忘记将样式文件包含在编译目标中。

实现基于 GLUT。虽然 GLUT 库已被废弃,但 FreeGLUT 实现仍在维护中。

本入门教程将不讨论 GLUT 的缺点(例如,无法控制主循环,无法访问显示指针等)或“最新”OpenGL 包装器(如 GLFW)的优点。它将仅展示如何避免陷阱并使 NoesisGUI IntegrationSample 在 Linux 上运行。

Using the Code

下载的 NoesisGUI-ManagedSDK-2.0.2f1.zip 包含,除其他外:

  • 各种平台的库二进制文件(见图)
  • 托管包装器 DLL,以及
  • NoesisGUI IntegrationSample 的 Windows C# 版本,位于 Src/Samples/GL

Bin/Linux_x86_64/libNoesis.soBin/NoesisManaged.dllSrc/Samples/GL/* 文件 是本文项目的基本组成部分。

注意:几乎每天都会提供新/更新的 NoesisGUI-ManagedSDK 可供下载,其中包含重大更改。

本文基于 NoesisGUI-ManagedSDK-2.0.2f1.zip

C# 项目

本文提供了两个项目:NoesisGUI-GLUTWrapperLibNoesisGUI-IntegrationSample

NoesisGUI-GLUTWrapperLib 项目将 C++ 粘合代码编译为动态库 libGLUTErapper.so,用于 GLUT 调用。

NoesisGUI-IntegrationSample 项目将 NoesisGUI IntegrationSample 编译到 MONO/.NET 4.5 框架。这些文件包括:

  • libNoesis.so,包含 NoesisGUI 框架
  • libGLUTWrapper.so,包含来自 NoesisGUI-GLUTWrapperLib 项目的 GLUT 调用的粘合代码
  • libGLUTWrapper.cs,包含 libGLUTWrapper.so 调用的托管包装器,或者基于 OpenGL.cs 的完全托管的 GLUT 调用粘合代码
  • GLUTWrapper.cs,包含方便的应用程序创建的协议和方法,基于 libGLUTWrapper.cs
  • OpenGL.cs,包含 GLUT、GLU、GL、libX11 和 llibc API 协议,用于从托管代码调用这些库,以及
  • Program.cs,包含应用程序代码,基于 GLUTWrapper.csOpenGL.cs

可以轻松地在 libGLUTWrapper.cs 的行为之间切换,在 libGLUTWrapper.so 调用和基于 OpenGL.cs 的完全托管的 GLUT 调用粘合代码之间切换。在文件顶部,您会找到这三行:

// If USE_FULLY_MANAGED is unset, libGLUTWrapper.cs works as an managed wrapper for libGLUTWrapper.so.
// If USE_FULLY_MANAGED is set, libGLUTWrapper.cs works completely managed based on OpenGL.cs.
#define USE_FULLY_MANAGED

消息处理问题

Noesis 提供的 NoesisGUI IntegrationSample 的 Windows C# 版本在 Src/Samples/GL/GLUTWrapper/GLUTWrapper.cpp 中包含 C++ 粘合代码用于 GLUT 调用。在我的环境中,这段 C++ 粘合代码负责:

  • combobox 的下拉列表仅通过强制重绘(例如,窗口大小更改)来显示,以及
  • 应用程序在两到三次此类 combobox 操作后挂起。

这就是为什么我将 C++ 代码转移到类 libGLUTWrapper 中的完全托管的替代 C# 代码中,并且 NoesisGUI IntegrationSample 可以正常运行。

唯一无法转移的是:调用 C 运行时函数 atexit()

但是,通过使用 FreeGLUT 而不是 GLUT,调用以下代码会更好:

GLUT.SetOption(FreeGLUT.GLUT_ACTION_ON_WINDOW_CLOSE, FreeGLUT.GLUT_ACTION_CONTINUE_EXECUTION);

并在退出后进行清理

GLUT.MainLoop();

而不是调用 atexit()

IntegrationSample 在退出 GLUT.MainLoop() 后不需要清理,但如果您的应用程序需要,可以使用 FreeGLUT 相对于 GLUT 提供的额外可能性。

以下是 libGLUTWrapper 类中受影响的方法(请参阅注释块):

/// <summary>
/// Register all callbacks and run the GL message loop.
/// </summary>
internal static void GLUT_Run()
{
    GLUT.DisplayFunc(GLUT_Display);
    GLUT.ReshapeFunc(GLUT_Resize);
    GLUT.MouseFunc(GLUT_MouseButton);
    GLUT.MotionFunc(GLUT_MouseMove);
    GLUT.PassiveMotionFunc(GLUT_MouseMove);
    GLUT.KeyboardFunc(GLUT_KeyboardDown);
    GLUT.KeyboardUpFunc(GLUT_KeyboardUp);
    GLUT.SpecialFunc(GLUT_KeyboardSpecialDown);
    GLUT.SpecialUpFunc(GLUT_KeyboardSpecialUp);

    // ==== Register an exit proc only from native code (never from managed code)! ====
    // The purpose of an exit proc is to clean up/free resources directly before the
    // library is unloaded. To avoid concurrency issues or unexpected behavior, it must
    // be guaranteed that NO other threads may still access the resources to free by
    // now. And this can not be guaranteed for managed code.
    // atexit(new ExitProcDelegate(GLUT_Close));

    GLUT.MainLoop();
}

输入法问题

我是德国人。德语有 umlaut 字符。

  • 我无法在 NoesisGUI IntegrationSample 原始示例代码的文本框中输入任何 umlaut 字符。
  • 注册到 FreeGLUT 的按键按下/弹起回调没有接收到 umlaut 字符。

这是因为 NoesisGUI 和 FreeGLUT 目前不能按预期/要求处理 Unicode 字符(有关详细信息,请参阅 使用 X11 Display 获取 UTF-8 输入)。

对于 FreeGLUT,代码是开源的——我们可以看一看:

...

/* Check for the ASCII/KeySym codes associated with the event: */
len = XLookupString(&event.xkey, asciiCode, sizeof(asciiCode),
                    &keySym, &composeStatus);

/* GLUT API tells us to have two separate callbacks... */
if (len > 0)
{
    /* ...one for the ASCII translatable keypresses... */
    if (keyboard_cb)
    {
        fgSetWindow( window );
        fgState.Modifiers = fgPlatformGetModifiers( event.xkey.state );
        keyboard_cb(asciiCode[ 0 ],
                    event.xkey.x, event.xkey.y);
        fgState.Modifiers = INVALID_MODIFIERS;
    }
}
else

...

由于没有 XOpenIM()XGetIMValues()XCreateIC()XSetICFocus()Xutf8LookupString() 调用,而我的环境的区域设置为 de_DE.UTF-8,因此使用 XLookupString() 调用时,UTF-8 键码被**错误处理**了。

为了避免此陷阱,我在应用程序的 MainClassRun() 方法中检查并调整了区域设置,以适应 NoesisGUI 和 FreeGLUT 的按键处理,因为 NoesisGUI 和 FreeGLUT 的按键处理无法根据区域设置进行调整。

private void Run(string[] args)
{
    // Check the locale of the runtime library and - if necessary - adjust it.
    sbyte[] requestedLocale  = C.StringToSByteArray("");
    IntPtr  currentLocale    = C.setlocale((int)C.LocaleCategory.LC_ALL, requestedLocale);
    // Report unset locale.
    if (currentLocale == IntPtr.Zero)
    {
        // Report unsupported C-language translation.
        Console.WriteLine("The c runtime library localization is currently not supported. " +
            "This might result in unexpected key code to character translation.");
    }
    else
    {
        String localeString = currentLocale != IntPtr.Zero ?
           System.Runtime.InteropServices.Marshal.PtrToStringAuto (currentLocale) : "";
        // Report not configured C-language translation.
        if (string.IsNullOrEmpty(localeString))
            Console.WriteLine("The c runtime library locale is currently not configured. This " +
                "might result in unexpected key code to character translation.");
        // Report minimal environment for C-language translation.
        else if (localeString == "C" || localeString == "POSIX")
            Console.WriteLine("The c runtime library locale is currently configured as '" +
                currentLocale +
                "'. This might result in unexpected key code to character translation.");

        if (localeString == "de_DE.UTF-8")
        {
            requestedLocale = C.StringToSByteArray("de_DE");
            currentLocale   = C.setlocale((int)C.LocaleCategory.LC_ALL, requestedLocale);
            localeString    = currentLocale != IntPtr.Zero ?
                System.Runtime.InteropServices.Marshal.PtrToStringAuto (currentLocale) : "";
        }
    }

    if (!X11.XSupportsLocale())
    {
        Console.WriteLine("The X11 runtime library localization is currently not supported. " +
            "This might result in unexpected key code to character translation.");
    }
    else
    {
        sbyte[] requestedModifier  = new sbyte[] {(sbyte)'\0'};
        IntPtr currentModifier = X11.XSetLocaleModifiers(requestedModifier);

        if (currentModifier == IntPtr.Zero)
        {
            // Report unset X11-language translation.
            Console.WriteLine("The X11 runtime library locale modifier for the XIM is currently" +
                " not set. This might result in unexpected key code to character translation.");
        }
        else
        {
            String modifierString = (currentModifier != IntPtr.Zero ?
                System.Runtime.InteropServices.Marshal.PtrToStringAuto(currentModifier) : "");
            // Report unset X11-language translation.
            if (string.IsNullOrEmpty(modifierString))
                Console.WriteLine("The X11 runtime library locale modifier for the XIM is currently" +
                    " not set. This might result in unexpected key code to character translation.");
            // Report non-standard environment for X11-language translation.
            else if (modifierString != "@im=local")
                Console.WriteLine("The X11 runtime library locale modifier for the XIM is currently" +
                    " not set. This might result in unexpected key code to character translation.");
        }
    }

    // ==========================================================================
    // Internationalization issues must be completed before any GLUT, GLU or GL
    // Initialization starts.
    // ==========================================================================

    GLUT.Init(args.Length, args);

    ...

本质上,我将区域设置从 de_DE.UTF-8 改为 de_DE,这将支持的按键码范围从 UTF-8 切换到 ISO-8859-1。我相信,这种方法也可以应用于其他语言。一个示例:

  • 使用 de_DE.UTF-8 时,umlaut 'ö' 被处理成**错误的键码** 195 - 结果是字母 'Ã'。
  • 使用 de_DE 时,umlaut 'ö' 被处理成**正确的键码** 246 - 结果是字母 'ö'。

通过这些更改,NoesisGUI IntegrationSample 使用 NoesisStyle.xaml 样式可以正常工作。

SimpleStyle.xamlWindowsStyle.xaml 样式使用备用字体(目前我还没有弄清楚它是在哪里配置的),该字体不提供 umlaut 的字形(而 NoesisStyle.xaml 样式使用 Roboto 字体)。

我解决这个问题所做的就是,在 *.xml 样式文件的最后一个 ColorBrush 定义之后,添加一个非常小的 ContentControl 样式,例如:

<SolidColorBrush x:Key="WindowBackgroundBrush" Color="#FFF"/>

<Style x:Key="RootContainerStyle" TargetType="{x:Type ContentControl}">
    <Setter Property="FontFamily" Value="Fonts/#Roboto"/>
    <Setter Property="FontSize" Value="12"/>
    <Setter Property="FontStyle" Value="Normal"/>
    <Setter Property="FontWeight" Value="Normal"/>
</Style>

因此,调整后的 WindowsStyle.xaml 样式结果如下:

提示:不要忘记将字体文件包含在编译目标中。

关注点

看来我终于找到了一个现代的(基于 XAML 且支持 3D/加速图形)跨平台 GUI 框架。我会关注它,很可能会深入研究 NoesisGUI 的可能性。

历史

  • 2018 年 3 月 15 日:初始版本
  • 2018 年 3 月 18 日:为替代样式添加了截图,并添加了关于样式使用的字体的信息。
© . All rights reserved.