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

使用 Roma Widget Set (C# X11) 编程 - 一个零依赖的 GUI 应用程序框架 - 编程技术。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (13投票s)

2014年5月13日

CPOL

19分钟阅读

viewsIcon

37026

如何在 C# 中高效开发 Linux/Unix (X11) GUI 应用程序,而无需依赖 GTK 或 KDE 等 GUI 框架。简单小部件的描述。

引言

本文描述了一些与 Roma 小部件集 (Xrw) 相关的编程技术 - 用于扩展小部件集或将其应用于应用程序编程。创建本文是因为整个主题超过50页,我决定将其拆分为三部分,使用 ,并拆分为四部分,使用 。使用 我将完整的 API 描述移至独立的 HTML 文档(作为 Xrw 项目的一部分),并重构了所有四篇文章,使阅读更具娱乐性和刺激性

  • 原始文章:编程 Roma 小部件集 (C# X11) - 零依赖 GUI 应用程序框架 - 简介。它包含对小部件集功能的简短解释。(在  之前:~ 基础。它包含一般描述。)本文应始终是首选的起点。
  • 第一次拆分:编程 Roma 小部件集 (C# X11) - 零依赖 GUI 应用程序框架 - 小部件集。由于“枯燥的”API 文档不断增长,API 文档已与  一起移至单独的 HTML 文档(作为 Xrw 项目的一部分),并且本文重点简要介绍了所有小部件。(在  之前:~ 内在小部件。它只包含内在小部件的 API 参考描述。)
  • 第二次拆分:编程 Roma 小部件集 (C# X11) - 零依赖 GUI 应用程序框架 - 编程技术。由于“枯燥的”API 文档不断增长,API 文档已与  一起移至单独的 HTML 文档(作为 Xrw 项目的一部分),并且本文重点介绍了编程技术。(在  之前:~ 简单小部件。它只包含简单小部件的 API 参考描述。)
  • 第三次拆分:编程 Roma 小部件集 (C# X11) - 零依赖 GUI 应用程序框架 - MVVM/XAML 支持。由于“枯燥的”API 文档不断增长,API 文档已与  一起移至单独的 HTML 文档(作为 Xrw 项目的一部分),并且本文重点介绍了 Xrw 对 MVVM/XAML 的支持。(在  之前:~ 复合小部件。它只包含复合小部件的 API 参考描述。)

下面描述的所有功能都可以标记为

  • 0.1 版本可用,
  • 0.2 版本可用,
  • 0.3 版本可用,
  • 0.4 版本可用,
  • 0.5 版本可用,
  • 0.6 版本可用,
  •  0.7 版本可用,
  •  0.8 版本可用,
  •  0.9 版本可用。
  • 0.2 版本禁用,
  • 0.3 版本禁用,
  • 0.4 版本禁用,
  • 0.5 版本禁用,
  • 0.6 版本禁用,
  •  0.7 版本禁用,
  •  0.8 版本禁用,
  •  0.9 版本禁用。

主题支持

该框架具有通用主题支持和这些预定义主题

  • XrwTheme.GeneralStyle.WinClassic(在 中命名为 XrwTheme.GeneralStyle.Win95;看起来像 Windows 95/98/Me/2K,有点像 Motif - 经典灰色 3D),
  • XrwTheme.GeneralStyle.WinLuna(类似于 Windows XP / MS Office 2007),
  • XrwTheme.GeneralStyle.WinRoyale(类似于 Windows Vista/7 / MS Office 2010),
  • XrwTheme.GeneralStyle.WinMidori(类似于 Windows 8/8.1 / MS Office 2013)和
  • XrwTheme.GeneralStyle.Gtk2Clearlooks(看起来像 Gnome 2.30.0 Clearlooks - 典型的 GTK 2)。

主题支持涵盖颜色、几何形状和图像。

为了为特定应用程序设计不常见的 GUI,几乎所有受主题影响的小部件属性也可以通过单独的值覆盖。或者可以创建并应用一个新的,替代的 XrwTheme.GeneralStyle

XrwTheme.GeneralStyle.WinClassic 的外观和感觉通过“文件选择”对话框演示。

XrwTheme.GeneralStyle.Gtk2Clearlooks 的外观和感觉通过“文件选择”对话框演示。

有关详细信息,请参阅 XrwTheme 类的 HTML 文档,并参阅 X11Graphic 类以了解支持的库存项目。

字体支持

大多数 Xfree86 安装的默认字体是 "-misc-fixed-medium-r-semicondensed--13-*-*-*-*-*-*"。因此,XrwTheme 类预定义了这些默认字体规格

DefaultFontName        = "-misc-fixed-medium-r-semicondensed--13-*-*-*-*-*-*";
DefaultItalicFontName  = "-misc-fixed-medium-o-semicondensed--13-*-*-*-*-*-*";
DefaultBoldFontName    = "-misc-fixed-bold-r-semicondensed--13-*-*-*-*-*-*"; 

这些字体几乎保证在每个 X 服务器上都可用,但它们是等宽位图字体,看起来不是很智能。

图片显示了 -misc-fixed- 字体输出示例。

此版本引入了方便的方法来更改预定义的字体规格为用户定义的字体规格,并确保所有小部件的 GC(图形上下文)初始化都使用 DefaultFontName 字体规格。

  • TrySetDefaultFontDefaultFontName 设置为用户定义的字体规格,成功时(字体可用)返回 true,否则保持 DefaultFontName 不变并返回 false。
  • TrySetDefaultItalicFontDefaultItalicFontName 设置为用户定义的字体规格,成功时(字体可用)返回 true,否则保持 DefaultItalicFontName 不变并返回 false。
  • TrySetDefaultBoldFontDefaultBoldFontName 设置为用户定义的字体规格,成功时(字体可用)返回 true,否则保持 DefaultBoldFontName 不变并返回 false。

建议在应用程序 shell 创建之后,小部件层次结构构建之前设置用户定义的字体规格。

public static void Main ()
{
    XrwTheme.Style = XrwTheme.GeneralStyle.Gtk2Clearlooks;
    
    Point assignedPosition = new Point (0, 0);
    Size  assignedSize     = new Size  (353, 480);
    X11Window appWindow    = new X11Window(ref assignedPosition, ref assignedSize);
    
    // Set the preferred font before the widget hierarchy is build up,
    // but after application shell creation.
    XrwTheme.TrySetDefaultBoldFont (appWindow.Display, appWindow.GC,
        "-*-helvetica-bold-r-normal--12-*-*-*-*-*-*");
    XrwTheme.TrySetDefaultItalicFont (appWindow.Display, appWindow.GC,
        "-*-helvetica-medium-o-normal--12-*-*-*-*-*-*");
    XrwTheme.TrySetDefaultFont (appWindow.Display, appWindow.GC,
        "-*-helvetica-medium-r-normal--12-*-*-*-*-*-*");
    
    appWindow.Run ();
}

此版本为 XrwTheme 类预定义引入了 -*-helvetica- 字体,因为它们比 -misc-fixed- 字体看起来更智能。

图片显示了 -*-helvetica- 字体输出示例。

支持 16 位颜色模型

支持 16 位颜色模型的通用方法是为应用程序使用单独的视觉和颜色映射。在这种特定情况下,它基于(虚拟)24 位颜色模型(硬件和 X11 服务器具有 24 位颜色功能),而 X 服务器在 16 位颜色模式下运行。没有(物理)16 位颜色模型(硬件和 X11 服务器没有 24 位颜色功能)的经验。

必要的代码,特别是 XrwCore.InitializeApplicationShellWindow()XrwCore.InitializeTransientShellWindow()XrwCore.InitializeOverrideShellWindow() 已经由 准备好。以下更改已使用 实现,以最终提供对这种特定 16 位颜色模型情况的支持

  • 将库存图标的颜色深度降低到 15 位(一定需要,但建议)
  • 将应用程序图标的颜色深度降低到 15 位(必需)。
  • 更正 XrwApplicationFramework.SetWmShellIcon()(参见“使用 修复”第 3 项)。
  • 更正 X11Graphic.CreateIndependentGraphicPixmap()(参见“使用 修复”第 4 项)。

我的 OPEN SUSE 11.3 Linux 32 位 EN 上的 /etc/X11/xorg.conf.d/50-screen.conf 已修改如下,以测试 16 位颜色模型支持

Section "Screen"
  Identifier "Default Screen"
  Device "Default Device"

  ## Doesn't help for radeon/radeonhd drivers; use magic in
  ## 50-device.conf instead
  Monitor "Default Monitor"

  # DefaultDepth 32 Chrashing!
  # DefaultDepth 24 # Running!
  DefaultDepth 16 # Running!
  # DefaultDepth 15 Chrashing!
  # DefaultDepth 8 # Running, but ugly!

  #############################################################################
  # Use one of these GRUB start options to repair a crashing X11 session:
  # - vga=ask: This option allows you to select the mode for the video adaptor.
  # - init=/bin/sh: Run the program /bin/sh (the shell) instead of init.
  #############################################################################
EndSection 

我的 OPEN SUSE 12.3 Linux 64 位 DE 上的(“X -configure”生成的)/etc/X11/xorg.conf 已修改如下,以测试 16 位颜色模型支持

Section "Screen"
  ...

  # DefaultDepth 32 Chrashing!
  # DefaultDepth 24 # Running!
  DefaultDepth 16 # Running!
  # DefaultDepth 15 Running with Xfce only!
  # DefaultDepth 8 # Running, but ugly!
EndSection 

国际化

支持国际化文本输出

有一篇关于 I18N 的好文章。其中第 13.1 TWM -- 使用 XFontSet 而不是 XFontStruct 是 X11 应用程序国际化文本输出实现的综合指南。

Roma 小部件集内部架构的第一个重大变化是引入了 X11FontData 结构,该结构支持 XFontSet 作为 XFontStruct 的替代,并提供 X11Surface.TextBoundings() 作为所有文本测量的单一入口点,以及 X11Surface.DrawString() 作为所有文本输出的单一入口点。

内部架构的第二个重大变化是在字体分配期间提供 XFontSet 作为 XFontStruct 的替代。根据 X 服务器和 C 运行时的 I18N 功能,XrwApplicationShellUseFontSet 属性设置为 true(或 false),并使用 XFontSet 而不是 XFontStruct(或不使用)。字体分配通过 XrwCorePrepareFont() 方法实现。

后续方法的故事很快就讲完了:在检查先决条件后,测试 FontData 是否要设置为应用程序的默认字体集/字体,或者当前的 FontData 是否已经是最新的。否则,加载并分配请求的字体集(如果 XrwApplicationShellUseFontSet 属性为 true)或字体。为了提供快速的几何计算,字体集/字体的最大高度、上升和下降也与 FontData 一起提供。

XrwApplicationShell 的字体初始化

/// <summary>Set a new default font (associate the indicated font with the graphics
/// context).</summary>
/// <param name="fontSpecification">The font specification to set as new default
/// font.<see cref="System.String"/></param>
/// <returns>True on success, or false otherwise.<see cref="System.Boolean"/></returns>
public virtual bool SetFont (string fontSpecification)
{    X11.X11FontData fontData = null;
     PrepareFont (fontSpecification, ref fontData);

    if (fontData == null)
        return false;
    _fontData = fontData;
    return true;
}

XrwCore 的字体准备

/// <summary>Prepare a new font for usage.</summary>
/// <param name="fontSpecification">The font specification to set as new font.<see cref="System.String"/></param>
/// <param name="fontData">The font data to set with the new font.<see cref="X11.X11FontData"/></param>
/// <returns>True on success, or false otherwise.<see cref="System.Boolean"/></returns>
protected bool PrepareFont (string fontSpecification, ref X11.X11FontData fontData)
{
    // Check prerequisits.
    if (_surface.Display == IntPtr.Zero)
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
            "::PrepareFont () Can not set a fontset/font to undefined display.");
        return false;
    }
    if (string.IsNullOrEmpty (fontSpecification))
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
            "::PrepareFont () Can not set a fontset/font with empty specification.");
        return false;
    }
    XrwApplicationShell appShell = ApplicationShell;
    if (appShell == null)
    {
        SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
            "::PrepareFont () Can not set a fontset/font for a widget/gadget " +
            "that is not associated with an application shell.");
        return false;
    }
    
    // If fontset/font is equal to the current fontset/font, skip.
    if (fontData != null && fontData.FontSpecification == fontSpecification)
    {
        SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
            "::PrepareFont () Skip to reset fontset/font from '" + fontData.FontSpecification +
            "' to '" + fontSpecification + "'.");
        return true;
    }
    
    // If fontset/font is equal to the application's default fontset/font, assign a reference copy.
    if ((appShell.FontData != null ?
         appShell.FontData.FontSpecification == fontSpecification : false) == true)
    {
        fontData = appShell.FontData;
        return true;
    }
    
    // If fontset/font is not equal to the application's default fontset/font and
    // not equal to the current fontset/font, load and assign fontset/font.
    // Use fontset, if supported.
    return X11FontService.PrepareFont (fontSpecification, _surface.Display,
                                       appShell.UseFontset, ref fontData);
}

X11FontService 的字体准备

/// <summary>Prepare a font or fonteset for utilization with Xrw.</summary>
/// <param name="fontSpecification">The font specification, that identifies a font/fontset.<see cref="System.String"/></param>
/// <param name="x11display">The display pointer, that specifies the connection to the X server.<see cref="IntPtr"/></param>
/// <param name="useFontset">The flag defining whether to use a fontset or a single font.<see cref="System.Boolean"/></param>
/// <param name="fontData">The resulting font data on success, or null otherwise.<see cref="X11.X11FontData"/></param>
/// <returns>True on success, or false otherwise.<see cref="System.Boolean"/></returns>
public static bool PrepareFont (string fontSpecification, IntPtr x11display, bool useFontset, ref X11.X11FontData fontData)
{
    fontData = null;
    
    // Check font cache.
    foreach (KeyValuePair<FontDataKey, X11FontData> loadedFont in _loadedFonts)
    {
        if (loadedFont.Key.FontSpecification == fontSpecification &&
            loadedFont.Key.X11Display == x11display && loadedFont.Key.UseFontset)
        {
            fontData = loadedFont.Value;
            return true;
        }
    }
    
    FontDataKey key = new FontDataKey (fontSpecification, x11display, useFontset)
    ...

开始加载请求的字体集。如果请求的 fontSpecification 未完全对应现有字体集,则逐步模糊化 fontSpecification,直到找到对应的现有字体集。这从拉伸开始,然后是粗细和倾斜。如果失败,则加载字体服务器的备用字体集。

    ...
    // Load fontset, if fontset isn't supported.
    if (useFontset)
    {
        IntPtr  missingCharsetList;
        TInt    missingCharsetCount;
        X11.XID fontsetResourceId = X11lib.XCreateFontSet (x11display, fontSpecification, out missingCharsetList,
                                                           out missingCharsetCount, IntPtr.Zero);
        // Check whether directly matching fontset has been loaded,
        // and - if not - load the most similar fontset (fuzzy).
        int fuzzyFactor = 0;
        string fuzzyFontSpecification = (fontsetResourceId == (X11.XID)0 ? fontSpecification : null);
        while (fontsetResourceId == (X11.XID)0 && fuzzyFactor < 3)
        {
            string lastFuzzyFontSpecification = fuzzyFontSpecification;
            if (fuzzyFactor == 0)
                fuzzyFontSpecification = X11FontData.ModifyFontSpecificationStretch (fuzzyFontSpecification, "*");
            if (fuzzyFactor == 1)
                fuzzyFontSpecification = X11FontData.ModifyFontSpecificationWieght  (fuzzyFontSpecification, "*");
            if (fuzzyFactor == 2)
                fuzzyFontSpecification = X11FontData.ModifyFontSpecificationSlant   (fuzzyFontSpecification, "*");
            
            fuzzyFactor++;
            // Safe time if no change has been made.
            if (lastFuzzyFontSpecification == fuzzyFontSpecification)
                continue;
            
            if (!string.IsNullOrEmpty(lastFuzzyFontSpecification) && lastFuzzyFontSpecification.Trim() != "")
            {
                fontsetResourceId = X11lib.XCreateFontSet (x11display, fuzzyFontSpecification,
                                                           out missingCharsetList,
                                                           out missingCharsetCount, IntPtr.Zero);
                if (fontsetResourceId != (X11.XID)0)
                {
                    SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                                       "::PrepareFont () Fuzzy load fontset with specification '" +
                                       fuzzyFontSpecification + "' " +
                                       "instead of '" + fontSpecification + "' succeeded.");
                }
            }
        }
        
        // Check whether directly matching or most similar fontset has been loaded,
        // and - if not - load a fallback fontset.
        string extFontSpecification = null;
        if (fontsetResourceId == (X11.XID)0)
        {
            // Let the font server guess a fallback fontset.
            if (!string.IsNullOrEmpty(fontSpecification) && fontSpecification.Trim() != "" &&
                !fontSpecification.Trim().EndsWith (",*"))
            {
                extFontSpecification = fontSpecification + ",*";

                SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                                   "::PrepareFont () Can not load a fontset with specification '" +
                                   fontSpecification + "'.");
                SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                                   "::PrepareFont () Retry to load a fontset with specification '" +
                                   extFontSpecification + "'.");

                fontsetResourceId = X11lib.XCreateFontSet (x11display, extFontSpecification,
                                                           out missingCharsetList,
                                                           out missingCharsetCount, IntPtr.Zero);
            }
            // The font specification already includs a joker to guess a fallback fontset.
            else
            {
                SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                                   "::PrepareFont () Can not load a fontset with specification '" +
                                   fontSpecification + "'.");
                
                // No success at all - even with a guess of a fallback fontset!
                return false;
            }
        }

        // Check whether matching fontset has been loaded.
        if (fontsetResourceId == (X11.XID)0)
        {
            SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                               "::PrepareFont () Can not load a fontset with specification '" +
                               extFontSpecification + "'.");
            
            // No success at all - even with a guess of a fallback fontset!
            return false;
        }
        ...

报告最终加载的字体集和缺失的字符集。计算一些字体集属性并创建 fontData

        ...
        if (!string.IsNullOrEmpty (extFontSpecification))
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching fontset for specification '" +
                               fontSpecification + "' " +
                               "using specification '" + extFontSpecification + "'.");
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching fontset for specification '" +
                               fontSpecification + "' " +
                               "using specification '" + fuzzyFontSpecification + "'.");
        else
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching fontset for specification '" +
                               fontSpecification + "'.");
        
        for (int countCharSet = 0; countCharSet < (int)missingCharsetCount; countCharSet++)
        {
            IntPtr p = Marshal.ReadIntPtr (missingCharsetList, countCharSet * Marshal.SizeOf(typeof(IntPtr)));
            string s = Marshal.PtrToStringAuto (p);
            if (!string.IsNullOrEmpty (extFontSpecification))
                SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                                   "::PrepareFont () Fontset for specification '" +
                                   extFontSpecification + "' is missing font for charset  '" + s + "'.");
            else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
                SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                                   "::PrepareFont () Fontset for specification '" +
                                    fuzzyFontSpecification + "' is missing font for charset  '" + s + "'.");
            else
                SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                                   "::PrepareFont () Fontset for specification '" +
                                   fontSpecification + "' is missing font for charset  '" + s + "'.");
        }
        
        // Calculate maximum font height, ascent and descent.
        int                        ascent  = 0;
        int                        descent = 0;
        X11lib.XFontSetExtents     extents = X11lib.XExtentsOfFontSet (fontsetResourceId);
        
        X11lib.XFontStruct[]       fontStructArray;
        string[]                   fontNameArray;
        int                        maxFonts;
        
        maxFonts = X11lib.XFontsOfFontSet (fontsetResourceId, out fontStructArray, out fontNameArray);
        for (int countFonts = 0; countFonts < maxFonts; countFonts++)
        {
            if (ascent  < (int)fontStructArray[countFonts].ascent)
                ascent  = (int)fontStructArray[countFonts].ascent;
            if (descent < (int)fontStructArray[countFonts].descent)
                descent = (int)fontStructArray[countFonts].descent;
        }
        
        string finalFontSpecification = null;
        if (!string.IsNullOrEmpty (extFontSpecification))
            finalFontSpecification = extFontSpecification;
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            finalFontSpecification = fuzzyFontSpecification;
        else
            finalFontSpecification = fontSpecification;
        
        // Maximum font height, ascent and descent might be frequently used for calculation.
        fontData = X11FontData.NewFontSetData (finalFontSpecification, x11display, fontsetResourceId,
                                               (int)extents.max_logical_extent.height, ascent, descent);
        
        IntPtr gc = X11lib.XCreateGC (x11display, X11lib.XDefaultRootWindow (x11display), 0, IntPtr.Zero);
        if (gc != IntPtr.Zero)
        {
            fontData.SetTypicalCharWidth (AverageCharacterWidth(x11display, gc, fontData));
            X11lib.XFreeGC (x11display, gc);
        }
        _loadedFonts.Add (key, fontData);
        return true;
    }
    ...

如果不支持字体集,则开始加载请求的字体。如果请求的 fontSpecification 未完全对应现有字体,则逐步模糊化 fontSpecification,直到找到对应的现有字体。这从拉伸开始,然后是粗细和倾斜。如果失败,则加载字体服务器的备用字体。

    ...
    // Use font, if fontset isn't supported.
    else // of (useFontset)
    {
        // Load font and query font structure (to get maximum font height, ascent and descent).
        IntPtr fontStructure = X11lib.XLoadQueryFont (x11display, fontSpecification);
        
        // Check whether directly matching font has been loaded,
        // and - if not - load the most similar font (fuzzy).
        int fuzzyFactor = 0;
        string fuzzyFontSpecification = (fontStructure == IntPtr.Zero ? fontSpecification : null);
        while (fontStructure == IntPtr.Zero && fuzzyFactor < 3)
        {
            string lastFuzzyFontSpecification = fuzzyFontSpecification;
            if (fuzzyFactor == 0)
                fuzzyFontSpecification = X11FontData.ModifyFontSpecificationStretch (fuzzyFontSpecification, "*");
            if (fuzzyFactor == 1)
                fuzzyFontSpecification = X11FontData.ModifyFontSpecificationWieght  (fuzzyFontSpecification, "*");
            if (fuzzyFactor == 2)
                fuzzyFontSpecification = X11FontData.ModifyFontSpecificationSlant   (fuzzyFontSpecification, "*");
            
            fuzzyFactor++;
            // Safe time if no change has been made.
            if (lastFuzzyFontSpecification == fuzzyFontSpecification)
                continue;
            
            if (!string.IsNullOrEmpty(lastFuzzyFontSpecification) && lastFuzzyFontSpecification.Trim() != "")
            {
                fontStructure = X11lib.XLoadQueryFont (x11display, lastFuzzyFontSpecification);
                if (fontStructure != IntPtr.Zero)
                {
                    SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                                       "::PrepareFont () Fuzzy load font with specification '" +
                                       fuzzyFontSpecification + "' " +
                                       "instead of '" + fontSpecification + "' succeeded.");
                }
            }
        }
        
        // Check whether directly matching or most similar font has been loaded,
        // and - if not - load a fallback font.
        string extFontSpecification = null;
        if (fontStructure != IntPtr.Zero)
        {
            // Let the font server guess a fallback fontset.
            if (!string.IsNullOrEmpty(fontSpecification) && fontSpecification.Trim() != "" &&
                !fontSpecification.Trim().EndsWith (",*"))
            {
                extFontSpecification = fontSpecification + ",*";

                SimpleLog.LogLine (TraceEventType.Warning, CLASS_NAME +
                                   "::PrepareFont () Can not load a fontset with specification '" +
                                   fontSpecification + "'.");
                SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                                   "::PrepareFont () Retry to load a fontset with specification '" +
                                   extFontSpecification + "'.");

                fontStructure = X11lib.XLoadQueryFont (x11display, extFontSpecification);
            }
            // The font specification already includs a joker to guess a fallback fontset.
            else
            {
                SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                                   "::PrepareFont () Can not load a font with specification '" +
                                   fontSpecification + "'.");
                
                // No success at all - even with a guess of a fallback font!
                return false;
            }
        }
        
        // Check whether matching font has been loaded.
        if (fontStructure == IntPtr.Zero)
        {
            SimpleLog.LogLine (TraceEventType.Error, CLASS_NAME +
                               "::PrepareFont () Can not load a font with specification '" +
                               fontSpecification + "'.");
            // No success at all - even with a guess of a fallback font!
            return false;
        }
        ...

报告最终加载的字体。创建 fontData

        ...
        if (!string.IsNullOrEmpty (extFontSpecification))
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching font for specification '" +
                               fontSpecification + "' " +
                               "using specification '" + extFontSpecification + "'.");
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching font for specification '" +
                               fontSpecification + "' " +
                               "using specification '" + fuzzyFontSpecification + "'.");
        else
            SimpleLog.LogLine (TraceEventType.Information, CLASS_NAME +
                               "::PrepareFont () Successfully loaded best matching font for specification '" +
                               fontSpecification + "'.");
        
        X11lib.XFontStruct    fs  = (X11lib.XFontStruct)Marshal.PtrToStructure (fontStructure,
                                                                                typeof(X11lib.XFontStruct));

        string finalFontSpecification = null;
        if (!string.IsNullOrEmpty (extFontSpecification))
            finalFontSpecification = extFontSpecification;
        else if (!string.IsNullOrEmpty (fuzzyFontSpecification))
            finalFontSpecification = fuzzyFontSpecification;
        else
            finalFontSpecification = fontSpecification;
        
        // Maximum font height, ascent and descent might be frequently used for calculation.
        fontData = X11FontData.NewSingleFontData (finalFontSpecification, x11display, fs.fid,
                                                  (int)fs.ascent + (int)fs.descent,
                                                  (int)fs.ascent, (int)fs.descent);
        
        IntPtr gc = X11lib.XCreateGC (x11display, X11lib.XDefaultRootWindow (x11display), 0, IntPtr.Zero);
        if (gc != IntPtr.Zero)
        {
            fontData.SetTypicalCharWidth (AverageCharacterWidth(x11display, gc, fontData));
            X11lib.XFreeGC (x11display, gc);
        }
        _loadedFonts.Add (key, fontData);
        return true;
    }
}

应用程序或对话框窗口的解剖

应用程序窗口基于 XrwApplicationShell,对话框窗口基于 XrwDialogShell。两者都派生自抽象的 XrwWmShell,而 XrwWmShell 派生自 XrwComposite - 容器的基类,用于管理任意数量的子小部件。XrwWmShell 提供与窗口管理器的交互(窗口的移动、调整大小、关闭等)。由于 XrwComposite 没有集成的布局管理,因此建议为每个 XrwApplicationShellXrwDialogShell 实例分配一个 XrwBoxXrwGridFormXrwUniformGridXrwDockPanel 子项,以管理 shell 孙子小部件的布局。

由于 XrwWmShell 派生类将 XSetWindowAttributes 属性 bit_gravity 设置为 NorthWestGravity,并将窗口的背景颜色设置为 XrwTheme.GeneralBackgroundColor,因此 shell 的布局管理器小部件/小工具在重绘过程(ConfigureNotify 事件的结果 - 在窗口调整大小操作时观察到)中的闪烁效果已经最小化:大多数闪烁效果来自黑色(未绘制的)shell 背景和 shell 背景清理与管理器小部件/小工具重绘之间的时间延迟。

还有一些原因导致闪烁效果 - 参见 事件处理的特定方面 一章。

示例代码展示了如何将 XrwBox 用作对话框 shell 的管理器小工具。

public class XrwBitmapAndVectorFontSelectionDialog : XrwDialogShell
{
    // Define constants and member attributes.
    ...
 
    // Implement the constructor.
    public XrwBitmapAndVectorFontSelectionDialog (XrwApplicationShell parent,
                                                  ref Point assignedPosition,
                                                  ref Size assignedSize, string title)
        : base (parent, ref assignedPosition, ref assignedSize)
    {
        // Initialize member attributes.
        ...
 
        // Create shell's primary layout manager.
        XrwBox vboxMain = XrwBox.NewVBoxGadget (this);
        vboxMain.BorderWidth = XrwTheme.DlgShellPrimaryChildBorderWidth;
        vboxMain.BorderColor = _backgroundColorPixel;
        vboxMain.VertSpacing = XrwTheme.DlgShellPrimaryChildSpacing;
        AddChild (vboxMain);
 
        // Create shell's grandchildren.
        ...
    }
 
    // Implement the destructor, properties and methods.
    ...   
} 

对话框窗口通常包含一个带有操作按钮的操作区域 - 例如,取消确定

下一个示例代码展示了如何从对话框构造函数中提取并创建操作区域。

{

    ...

    // ---- Begin "Action" area.

    // Create a HBox to group the action buttons.
    XrwBox hboxActionArea = XrwBox.NewHBoxGadget (vboxMain);
    hboxActionArea.BorderWidth = 2;
    hboxActionArea.ChildAlign = 1.0F;
    hboxActionArea.HorzSpacing = XrwTheme.DlgShellPrimaryChildSpacing;
    hboxActionArea.BorderColor = hboxActionArea.BackgroundColorDark;
    vboxMain.AddChild (hboxActionArea);

    // Create and register Cancel button.
    X11Graphic cancelGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                    X11Graphic.StockIcon.Cancel16);
    XrwCommand cbCancel = XrwCommand.NewCommandWidget (hboxActionArea, "Cancel", cancelGraphic,
                                                       true, null, false);
    cbCancel.ExpandToMaxSiblingWidth = true;
    cbCancel.HorzTextAlign = 0.5F;
    cbCancel.Clicked += HandleCancelButtonClicked;
    hboxActionArea.AddChild (cbCancel);

    // Register "Cancel" button ation as input receiver.
    ICommand cmd0 = new RelayCommand (ProcessCancelButtonAction);
    Xrw.Utils.KeyGestureBinding kgb0 = new Xrw.Utils.KeyGestureBinding (cmd0,
        X11lib.XKeySym.XK_Escap,    System.Windows.Input.ModifierKeys.None);
    base._inputReceiver.Add (kgb0);
            
    // Create and register OK button.
    X11Graphic okGraphic = XrwTheme.GetGraphic (_surface.Display, _surface.ScreenNumber,
                                                X11Graphic.StockIcon.Ok16);
    XrwCommand cbOk = XrwCommand.NewCommandWidget (hboxActionArea, "OK", okGraphic,
                                                   true, null, false);
    cbOk.ExpandToMaxSiblingWidth = true;
    cbOk.HorzTextAlign = 0.5F;
    cbOk.Clicked += HandleOkButtonClicked;
    hboxActionArea.AddChild (cbOk);
                
    // Register "OK" button action as input receiver.
    ICommand cmd1 = new RelayCommand (ProcessOkButtonAction);
    Xrw.Utils.KeyGestureBinding kgb1 = new Xrw.Utils.KeyGestureBinding (cmd1,
        X11lib.XKeySym.XK_Return,   System.Windows.Input.ModifierKeys.None);
    Xrw.Utils.KeyGestureBinding kgb2 = new Xrw.Utils.KeyGestureBinding (cmd1,
        X11lib.XKeySym.XK_Num_Enter, System.Windows.Input.ModifierKeys.None);
    base._inputReceiver.Add (kgb1);
    base._inputReceiver.Add (kgb2);
            
    // ---- End "Action" area.

    ...

}

它看起来像这样

按钮回调连接到 Clicked 事件。

/// <summary> Handle the ButtonPress event. </summary>
/// <param name="source"> The widget, the ButtonPress event is assigned
/// to. <see cref="XrwRectObj"/> </param>
void HandleOkButtonClicked (XrwRectObj source)
{
    if ((source is XrwCommand) && !(source as XrwCommand).Focused)
        return;
    
    ProcessOkButtonAction (null);
}

/// <summary>The default 'action' implementation for XrwCommand to execute if triggered.</summary>
/// <param name="o">A parameter to use for any purpose.</param>
private void ProcessOkButtonAction (object o)
{
    _result = System.Windows.MessageBoxResult.OK;
    this.DefaultClose ();
    this.OnEnd (_result);
}

/// <summary> Handle the ButtonPress event. </summary>
/// <param name="source"> The widget, the ButtonPress event is assigned
/// to. <see cref="XrwRectObj"/> </param>
void HandleCancelButtonClicked (XrwRectObj source)
{
    if ((source is XrwCommand) && !(source as XrwCommand).Focused)
        return;
    
    ProcessCancelButtonAction (null);
}

/// <summary>The default 'action' implementation for XrwCommand to execute if triggered.</summary>
/// <param name="o">A parameter to use for any purpose.</param>
private void ProcessCancelButtonAction (object o)
{
    _result = System.Windows.MessageBoxResult.Cancel;
    this.DefaultClose ();
    this.OnEnd (_result);
}

由于此版本引入了按键手势绑定,最终的 Clicked 事件处理不是事件处理程序的一部分,而是交换到相应的操作方法。这提供了第二次使用操作方法的可行性。即使用 Xrw.Utils.KeyGestureBinding 作为键盘输入接收器。因此,对话框可以使用 [Escape] 键(等同于对话框的取消按钮)、[Enter] 或 [Return] 键(等同于对话框的确定按钮)关闭。

对话框基于瞬态 shell,通常从应用程序 shell 接管(无限)消息循环处理。为了通过关闭应用程序窗口完全清理此类活动对话框,它们必须覆盖瞬态 shell 的 DefaultClose() 方法以停止其(无限)消息循环处理。以下是对此的详细了解:XrwTransientShell 实现了这两个消息处理程序。

OnWmClose() 仅在通过窗口装饰的关闭按钮调用 shell 关闭时才被调用。其唯一目的是提供事件转发到注册的委托。即使它是虚拟的,附加功能也应通过委托而不是通过覆盖来实现。此外,这促进了代码重用。

DefaultClose() 应始终被调用,无论关闭是通过窗口装饰的关闭按钮、对话框内的小部件/小工具(如 [确定] 或 [取消] 按钮)还是键盘快捷键(如 [Return] 或 [Escape])触发。派生类可以使用 override 来准备返回值并终止其(无限)消息循环处理。

#region Event handler

/// <summary>Handle the ClientMessage event.</summary>
/// <param name="e">The event data.<see cref="XrwClientMessageEvent"/></param>
/// <remarks>Set XawClientMessageEvent.Result to nonzero to stop further event processing.</remarks>
internal virtual void OnWmClose (XrwClientMessageEvent e)
{
    WmShellCloseDelegate wmShellClose = WmShellClose;
    if (wmShellClose != null)
        wmShellClose (this, e);
}

/// <summary>Default processing for the Close event.</summary>
/// <returns>True to continue closing, false otherwise.</returns>
/// <remarks>This method should be overwritten, if transient shell calls the message loop
/// within Run(). Otherwise the message loop continues to run and the garbage collector
/// can't clean the application.</remarks>
public virtual bool DefaultClose ()
{
    if (_disposed == true)
        return true;
    
    // Attention: Avoid double dispose by calling Dispose() a second time within derived classes!

    this.ApplicationShell.RemoveTransientShell (this);
    Unrealize ();
    Dispose ();
    
    // Don't disconnect from X server - application shell is still running.
    
    return true;
}

#endregion

XrwTransientShell 派生类,例如 XrwFileSelectionDialog,应覆盖 DefaultClose() 以准备返回值并终止其(无限)消息循环处理。它还应注册并实现 HandleDialogClose(),以在关闭时提供一致的行为,无论关闭是通过窗口装饰的关闭按钮、对话框的 [取消] 按钮还是 [Escape] 键快捷键触发。

    WmShellClose += HandleDialogClose;
#region Overwritten methods (XrwTransientShell)

/// <summary>Default processing for the Close event.</summary>
/// <param name="source">The widget, the Close event is assigned to.<see cref="XrwTransientShell"/></param>
/// <returns>True to continue closing, false otherwise.</returns>
/// <remarks>This method should be overwritten, if transient shell calls the message loop.</remarks>
public override bool DefaultClose ()
{
    // FIRST: Ensure the message loop will be released.
    _result = System.Windows.MessageBoxResult.Cancel;
    
    // SECOND: Standard transient shell behaviour is appropriate.
    return base.DefaultClose ();
}

#endregion Overwritten methods (XrwTransientShell)

#region Event handler

/// <summary> Application specific processing of the ApplicationClose event. </summary>
/// <param name="source"> The widget, the WmShellClose event is assigned to. <see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawClientMessageEvent"/> </param>
/// <remarks> Set XawClientMessageEvent.Result to nonzero to stop further event processing. </remarks>
void HandleDialogClose (XrwRectObj source, XrwClientMessageEvent e)
{
    this.DefaultClose ();

    // Stop event processing here!
    e.Result = 1;
    
    this.OnEnd (_result);
}

...

弹出菜单的使用

弹出菜单的创建非常简单。以下是步骤

  • 创建弹出菜单 shell XrwSimpleMenuShell
  • XrwSme 类的菜单项添加到菜单 shell,并将回调注册到菜单项。
  • 强制 shell 的几何管理。
  • 创建菜单按钮 XrmMenuButton 并将其添加到父复合组件。
  • 将菜单 shell 注册到菜单按钮。

无需额外代码来处理弹出、布局、弹出关闭或选择。

图片显示了一个简单的 XrwMenuButton,带有左右(透明多色)位图 - 包括其弹出的菜单,该菜单基于一个包含两个带有左右(透明多色)位图的 XrwSmeXrwDialogShell

示例代码展示了如何创建前一张图片所示的简单弹出菜单。

XrwSimpleMenu _fileMenuShell = null;

...

// ---- Create popup menu.
Point origin   = new Point (20, 20);
Size  initSize = new Size  (-1, -1);
_fileMenuShell = new XrwSimpleMenu (this, ref origin, ref initSize);

// ---- Add menu entries.
X11Graphic menuEntryGraphicA = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.Information16);
X11Graphic menuEntryGraphicB = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.Question16);
XrwSme menuEntry1 = XrwSme.NewSmeGadget (_fileMenuShell, "File menu entry 1",
    menuEntryGraphicA, true, menuEntryGraphicB, true);
menuEntry1.ButtonRelease += HandleMenuEntry1ButtonRelease;
_fileMenuShell.AddChild (menuEntry1);
XrwSme menuEntry2 = XrwSme.NewSmeGadget (_fileMenuShell, "File menu entry 2",
    menuEntryGraphicA, true, menuEntryGraphicB, true);
menuEntry2.ButtonRelease += HandleMenuEntry2ButtonRelease;
_fileMenuShell.AddChild (menuEntry2);

// ---- Beautify popup menu.
_fileMenuShell.CalculateChildLayout ();
_fileMenuShell.SetFixedWidth  (_fileMenuShell.AssignedSize.Width);
_fileMenuShell.SetFixedHeight (_fileMenuShell.AssignedSize.Height);

// ---- Create menu button.
X11Graphic cbw0GraphicA = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.FileGeneric16);
X11Graphic cbw0GraphicB = XrwTheme.GetGraphic ( _display,_screenNumber,
    X11Graphic.StockIcon.FolderClose16);
XrwMenuButton commandFileMenu = XrwMenuButton.NewMenuButtonWidget
    (hboxFileRibbon, "File", cbw0GraphicA, true, cbw0GraphicB, true);
commandFileMenu.FrameType = XrwTheme.StaticFrameType;
commandFileMenu.FrameWidth = XrwTheme.StaticFrameWidth;
commandFileMenu.ExpandToAvailableHeight = true;

// ---- Register menu to menu button and insert menu button into parent widget.
commandFileMenu.Menu = _fileMenuShell;
hboxFileRibbon.AddChild (commandFileMenu); 

关闭应用程序窗口

为了向应用程序开发人员提供方便的 API,框架内部必须准备一些东西

1. 为了清理 XrwApplicationShell,注册了一个主 shell 的关闭委托

// Register close event.
this.WmShellClose += HandleApplicationShellCloseDefault;

...

/// <summary> Application specific processing of the WmShellClose event. </summary>
/// <param name="source"> The widget, the ApplicationClose event is
/// assigned to. <see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawClientMessageEvent"/> </param>
/// <remarks> Set XawClientMessageEvent.
/// Set result to nonzero to stop further event processing. </remarks>
void HandleApplicationShellCloseDefault (XrwRectObj source, XrwClientMessageEvent e)
{
     ...

     Dispose ();

     // Disconnect from X server.
     X11lib.XCloseDisplay (_surface.Display);
     _surface.SetDisplay (IntPtr.Zero);

     e.Result = 0;
}

2. 由于 OnClose 委托的实现是以相反的顺序调用注册的处理程序,因此清理过程总是从派生类返回到基类 - 并且基类处理程序必须是最后一个,因为它与 X11 服务器断开连接。

/// <summary> Handle the ClientMessage 'Close' event. </summary>
/// <param name="e"> The event data. <see cref="XawClientMessageEvent"/> </param>
/// <remarks> Set XawClientMessageEvent result to nonzero to stop further event processing. </remarks>
public void OnClose (XrwClientMessageEvent e)
{
    // Call the close delegates in reverse order!
    object[]    param = new object[] {this, e};
    Delegate[]    delegates = WmShellClose.GetInvocationList();
    for (int i=delegates.Length-1;i>=0;i--)
        delegates[i].DynamicInvoke (param);
        
    //WmShellCloseDelegate wmShellClose = WmShellClose;
    //if (wmShellClose != null)
    //    wmShellClose (this, e);
}

这使得资源能够被释放而不会产生内存泄漏。

标准对话框的使用

目前这些标准对话框可用

  • XrwMessageBox 用于仅通知对话框(除了在确定和取消之间选择之外没有输入)。
  • XrwFileSelectionDialog 用于单文件选择。
  • XrwBitmapAndVectorFontSelectionDialog 用于使用所有 X11 字体信息选择字体。

这是 XrwMessageBox 的图像,带有纯文本和标记文本,与 一起引入。

   

这是 XrwFileSelectionDialog 的图像。

这是 XrwBitmapAndVectorFontSelectionDialog 的图像。
(此对话框适用于使用 XLoadFontXDrawStringXDrawString16 的非国际化文本输出,但不适用于使用字体集的 I18N 文本输出。)

现在这些额外的标准对话框可用

这些是 XrwColorSelectionDialog 的图像,使用 16 种预定义颜色,以 8 列 2 行或 2 列 8 行显示颜色名称。

现在这个额外的标准对话框可用

这些是 XrwColorChooseDialog 的图像,使用一个笔记本页面用于标准颜色,另一个笔记本页面用于自定义颜色。

现在这个额外的标准对话框可用

这是 XrwFontSelectionDialog 的图像。

(此对话框适用于使用字体集、XCreateFontSetXwcDrawString 的国际化文本输出。)

XrwFileSelectionDialogXrwBitmapAndVectorFontSelectionDialogXrwColorSelectionDialogXrwColorChoseDialogXrwFontSelectionDialog 的创建非常直接。

示例代码展示了如何使用 XrwFileSelectionDialog

/// <summary> Handle the Clicked event. </summary>
/// <param name="source"> The widget, the Clicked event is assigned to. <see cref="XrwRectObj"/> </param>
void HandleFileSelectionDialogButtonClicked (XrwRectObj source)
{
    XrwFileSelectionDialog fileDialog = XrwFileSelectionDialog.
        NewFileSelectionDialog (this, "Mono Develop - File selection", Environment.CurrentDirectory);
    fileDialog.SetMinimumSize (fileDialog.AssignedSize);

    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (fileDialog, AppIconFilePath);
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (fileDialog);
    XrwDialogShell.DialogResult result = fileDialog.Run ();

    if (result == XrwDialogShell.Result.OK)
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME +
                               "::HandleFileSelectionDialogButtonClicked() " +
                               "File dialog closed with: OK, File selected is: " + fileDialog.SelectedFile);
        ApplicationFramework.WriteStatus ("File selection dialog closed with: OK");
    }
    else
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME +
                               "::HandleFileSelectionDialogButtonClicked() " +
                               "File dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("File selection dialog closed with: Cancel");
    }
}

下一个示例代码展示了如何使用 XrwBitmapAndVectorFontSelectionDialog

/// <summary> Handle the Clicked event. </summary>
/// <param name="source"> The widget, the Clicked event is assigned to. <see cref="XrwRectObj"/> </param>
void HandleFontDialogButtonClicked (XrwRectObj source)
{
    XrwBitmapAndVectorFontSelectionDialog fontDialog = XrwBitmapAndVectorFontSelectionDialog.
        NewBitmapAndVectorFontSelectionDialog (this, "Mono Develop - Font selection");
    fontDialog.SetMinimumSize (fontDialog.AssignedSize);
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (fontDialog, APPICON_FILEPATH);
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (fontDialog);
 
    XrwDialogShell.DialogResult result = fontDialog.Run ();
 
    if (result == XrwDialogShell.Result.OK)
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME + "::HandleFontDialogButtonClicked() " +
                               "Font dialog closed with: OK");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: OK");
    }
    else
    {
        if (XrwApplicationSettings.VERBOSE_OUTPUT_TO_CONSOLE)
            Console.WriteLine ("VERBOSE: " + CLASS_NAME + "::HandleFontDialogButtonClicked() " +
                               "Font dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: Cancel");
    }
}

下一个示例代码展示了如何使用 XrwColorSelectionDialog

/// <summary> Handle the Clicked event. </summary>
/// <param name="source"> The widget, the Clicked event is assigned to. <see cref="XrwRectObj"/> </param>
void HandleColorSelectionDialogButtonClicked (XrwRectObj source)
{
    XrwColorSelectionDialog colorDialog = XrwColorSelectionDialog.NewColorSelectionDialog8x2 (this,
                                              "Mono Develop - Color selection", 0x00ffffff);
    colorDialog.SetMinimumSize (colorDialog.AssignedSize); 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (colorDialog, APPICON_FILEPATH);    
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (colorDialog); 
 
    XrwDialogShell.DialogResult result = colorDialog.Run ();    
    if (result == XrwDialogShell.Result.OK)
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorSelectionDialogButtonClicked () Color dialog closed with: OK, " +
            "Color selected is: #{0:X000000}", colorDialog.SelectedColor);
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: OK");
    }
    else
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorSelectionDialogButtonClicked () Color dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: Cancel");
    }
}

下一个示例代码展示了如何使用 XrwColorChooseDialog

/// Handle the ButtonRelease event.
/// <param name="source" />The widget, the ButtonRelease event is assigned to. <see cref="XrwRectObj"/>
/// <param name="e" />The event data. <see cref="XawButtonEvent"/>
/// <remarks>Set XawButtonEvent.result to nonzero to stop further event processing. </remarks>
void HandleColorChooseDialogButtonRelease (XrwRectObj source, XrwButtonEvent e)
{
    XrwColorChooseDialog colorDialog = XrwColorChooseDialog.NewColorCooseDialog (this,
        "Mono Develop - Color choose", 0x00ffffff);
    colorDialog.SetMinimumSize (colorDialog.AssignedSize); 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's constructor sets the shell icon.
    // ApplicationFramework.SetWmShellIcon (colorDialog, APPICON_FILEPATH);    
 
    // This call has been required before version 0.6.
    // Starting with version 0.6 the dialog's Run() method registers the transient shell to the
    // application by this.AddTransientShell (colorDialog); 
 
    XrwDialogShell.DialogResult result = colorDialog.Run ();    
    if (result == XrwDialogShell.Result.OK)
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorChooseDialogButtonRelease () Color dialog closed with: OK, " +
            "Color selected is: #{0:X000000}", colorDialog.SelectedColor);
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: OK");
    }
    else
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
            "::HandleColorChooseDialogButtonRelease () Color dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Color selection dialog closed with: Cancel");
    }
}

下一个示例代码展示了如何使用 XrwFontSelectionDialog

/// <summary> Handle the ButtonRelease event. </summary>
/// <param name="source"> The widget, the ButtonRelease event is assigned to.<see cref="XrwRectObj"/> </param>
/// <param name="e"> The event data. <see cref="XawButtonEvent"/> </param>
/// <remarks> Set XawButtonEvent. Set result to nonzero to stop further event processing. </remarks>
void HandleVectorFontDialogButtonRelease (XrwRectObj source, XrwButtonEvent e)
{
    XrwFontSelectionDialog fontDialog =
        XrwFontSelectionDialog.NewFontSelectionDialog (this, "Mono Develop - GTK/Windows " +
                                                             "compatible font selection");
    fontDialog.Font = new Xrw.FontInfo ("Adobe Helvetica", 14, System.Drawing.FontStyleEx.Italic);
        
    System.Windows.MessageBoxResult result = fontDialog.Run ();
    
    if (result == System.Windows.MessageBoxResult.OK)
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
                        "::HandleVectorFontDialogButtonRelease () Font dialog closed with: OK");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: OK");
        
        // Get the font specification, that alows to load a specific font from X11 font server.
        // string fs = fontDialog.FontSpecification;
        // Get the font info to determine Strikeout and Underline.
        Xrw.FontInfo font = fontDialog.Font;
        if (font == null)
            return;
        
    }
    else
    {
        SimpleLog.LogLine (TraceEventType.Verbose, CLASS_NAME +
                           "::HandleVectorFontDialogButtonRelease () " +
                           "Font dialog closed with: Cancel");
        ApplicationFramework.WriteStatus ("Font selection dialog closed with: Cancel");
    }

    e.Result = 1;
}

对话框实例化后,建议进行此调用

  • dialog.SetMinimumSize(dialog.AssignedSize) 以防止尺寸下溢。

所有对话框都以一种方式计算它们的初始大小,即它是所有控件都能正确显示所需的最小大小。将初始大小设置为最小大小可以防止对话框调整到不合适的显示。

Xrw 的早期版本也建议在对话框实例化后调用这些方法

  • ApplicationFramework.SetWmShellIcon(dialog, APPICON_FILEPATH) 用于设置 shell 图标。
  • this.AddTransientShell(dialog) 用于将对话框注册到应用程序的瞬态 shell 列表。

但这些调用不再必要,因为它们现在在框架内部被调用。

XrwFileSelectionDialogXrwBitmapAndVectorFontSelectionDialogXrwColorSelectionDialogXrwFontSelectionDialog 实现为应用程序模态对话框。因此,调用 XrwDialogShell.DialogResult result = dialog.Run() 会等待对话框结束,并且可以随后评估 result

现在所有标准对话框都支持按键手势绑定。因此,对话框可以使用 [Escape] 键(如果对话框包含取消按钮)、[Enter] 或 [Return] 键(它们等同于对话框的确定按钮)关闭。

事件处理的特定方面

重绘性能问题和/或闪烁

在窗口调整大小过程中,窗口管理器会发出大量的 ConfigureNotify 事件。应用程序会为每个 ConfigureNotify 事件重新计算其子小部件的布局。新的大小又会导致相应的 Expose 事件。由于 X11 的异步绘图模型以及——特别是对于复杂布局来说,由于布局重新计算时间——重绘性能问题和/或闪烁是结果。

为了避免这种情况,Athena 和 Motif 为其小部件提供了事件压缩标志(compress_motion、compress_exposure、compress_enterleave)。但这并非令人满意的解决方案,因为要压缩的事件必须已经排队并且立即连续。我对这种方法的测试并未显示出显著的改进。

示例代码展示了 XrwApplicationShellDoEvent() 方法的一个片段,该片段说明了这种事件压缩方法。

if (xevent.type == XEventName.ConfigureNotify)
{
    // ***************************************************************************************
    // Why does the GUI flicker and is there a way out?
    // http://fixunix.com/xwindows/556305-resizing-motif-xaw-xlib-apps-vs-resizing-gtk-qt-
    // apps-how-tospeed-up-xlib-app.html
    // ***************************************************************************************

    // This compression approach requires, that compressible events are ALREADY in the queue.
    // This assumption is NOT very realistic!!!
    X11EventHelper.Matches = 0;
    X11lib.XCheckIfEvent (_display, ref X11EventHelper.Event,
                          X11EventHelper.CountConfigureMatchesProcPtr,
                          xevent.ConfigureEvent.window);
    if (X11EventHelper.Matches > 0)
    {
        Console.WriteLine ("INFORMATION: " + CLASS_NAME + "::DoEvent () // CONFIGURE " +
                           "found subsequent configure events for window " +
                           xevent.ConfigureEvent.window.ToString("x") + " and skip this event.");
        return true;
    }

    ...
}
else if (xevent.type == XEventName.Expose)
{
    if (xevent.ExposeEvent.count > 0)
        return true;
    
    // ***************************************************************************************
    // Why does the GUI flicker and is there a way out?
    // http://fixunix.com/xwindows/556305-resizing-motif-xaw-xlib-apps-vs-resizing-gtk-qt-
    // apps-how-tospeed-up-xlib-app.html
    // ***************************************************************************************

    // This compression approach requires, that compressible events are ALREADY in the queue.
    // This is assumption NOT very realistic!!!
    X11EventHelper.Matches = 0;
    X11lib.XCheckIfEvent (_display, ref X11EventHelper.Event,
                          X11EventHelper.CountExposeMatchesProcPtr, xevent.ExposeEvent.window);
    if (X11EventHelper.Matches > 0)
    {
        Console.WriteLine ("INFORMATION: " + CLASS_NAME + "::DoEvent () // EXPOSE " +
                           "found subsequent expose events for window " +
                           xevent.ExposeEvent.window.ToString("x") + " and skip this event.");
        return true;
    }

    ...
}

我发现避免重绘性能问题和/或闪烁的最佳方法是暂停每个 ConfigureNotify 事件几毫秒,并压缩在此暂停期间发出的所有 ConfigureNotify 事件。这会导致与暂停间隔相等的重绘延迟,但在调整大小过程中整体印象会更流畅。

示例代码展示了 XrwApplicationShellDoEvent() 方法的一个片段,该片段说明了事件暂停方法。

/// <summary>Define the suspension interval in milli-seconds.</summary>
private ulong           _compressConfigureBySuspend = 350;
    
/// <summary>Remember the point in time the last configuration took place.</summary>
private ulong           _lastConfigureShell = 0;
    
/// <summary>Keep the latest configuration event to process it after suspension.</summary>
private XConfigureEvent _lastConfigureEvent = new XConfigureEvent();

...


/// <summary> Process the topmost event and remove it from the event queue. </summary>
/// <returns> True if event processing must contionue, false otherwise. </returns>
public bool DoEvent()
{
    // Prevent event processing *** after dispose *** but *** before destruction ***.
    if (_display == IntPtr.Zero)
        return false;
    
    XEvent xevent = new XEvent ();
    
    // Ensure all events are queued.
    X11lib.XFlush (_display);
    
    // Check for suspended ConfigureEvent and process it after the suspension interval.
    if (X11lib.XQLength (_display) == 0 && _compressConfigureBySuspend > 0)
    {
        DateTime dt        = DateTime.Now;
        ulong timeStamp    = (ulong)(dt.Millisecond + dt.Second * 1000 + dt.Minute * 60000 +
                             dt.Hour * 3600000) + (ulong)dt.Day * (ulong)86400000;
        
        if (timeStamp - _lastConfigureShell > _compressConfigureBySuspend &&
            _lastConfigureEvent.window != IntPtr.Zero)
        {
            XrwConfigureEvent e = new XrwConfigureEvent (ref _lastConfigureEvent);
            OnConfigure (e);
            _lastConfigureEvent.window = IntPtr.Zero;
            _lastConfigureShell = timeStamp;
        }
        return true;
    }


    ...
}

_compressConfigureBySuspend 间隔可以根据特定的用例进行调整,以在延迟和闪烁之间找到最佳平衡。值为 0 会完全抑制事件暂停。

我发现的唯一缺点是 XrwPaned 无法从这种方法中受益。

毫无疑问,避免重绘性能问题和/或闪烁的最佳方法是使用双缓冲。这种方法留待以后使用,因为它违反了 Xrw 零依赖的承诺。

按键手势绑定

从此版本开始支持按键手势绑定。它可以用于向任何 XrwShell(通常是应用程序 shell 或对话框 shell)注册全局键盘快捷键,目标是任何 GUI 元素。此技术可用于例如提高应用程序(或对话框)的可访问性。通常,按键手势绑定的首选目标是菜单和功能区。示例代码展示了如何将按键手势绑定到 XrwRibbon 并选择特定的 XrwRibbonTab

...

    // -- Start connection of application global key gesture bindings.

    System.Windows.Input.ICommand      activateRibbonTab0_Action  = new RelayCommand
        (ActivateRibbonTab0_Action);
    Xrw.Utils.KeyGestureBinding        activateRibbonTab0_Gesture = new Xrw.Utils.KeyGestureBinding
        (activateRibbonTab0_Action, null, null, X11lib.XKeySym.XK_D,
         System.Windows.Input.ModifierKeys.Alt);
    ApplicationShell.InputReceiver.Add (activateRibbonTab0_Gesture);

    System.Windows.Input.ICommand      activateRibbonTab1_Action  = new RelayCommand
        (ActivateRibbonTab1_Action);
    Xrw.Utils.KeyGestureBinding        activateRibbonTab1_Gesture = new Xrw.Utils.KeyGestureBinding
        (activateRibbonTab1_Action, null, null, X11lib.XKeySym.XK_R,
         System.Windows.Input.ModifierKeys.Alt);
    ApplicationShell.InputReceiver.Add (activateRibbonTab1_Gesture);

    System.Windows.Input.ICommand      activateRibbonTab2_Action  = new RelayCommand
        (ActivateRibbonTab2_Action);
    Xrw.Utils.KeyGestureBinding        activateRibbonTab2_Gesture = new Xrw.Utils.KeyGestureBinding
        (activateRibbonTab2_Action, null, null, X11lib.XKeySym.XK_S,
     System.Windows.Input.ModifierKeys.Alt);
    ApplicationShell.InputReceiver.Add (activateRibbonTab2_Gesture);

    // -- End connection of application global key gesture bindings.

...

/// <summary>Activate ribbon tab 0 action to execute on kex gesture binding.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data
/// to be passed, this object can be set to null.<see cref="System.Object"/></param>
public void ActivateRibbonTab0_Action (object parameter)
{
    ribbon.SetSelectedTab (0);
}
/// <summary>Activate ribbon tab 1 action to execute on kex gesture binding.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data
/// to be passed, this object can be set to null.<see cref="System.Object"/></param>
public void ActivateRibbonTab1_Action (object parameter)
{
    ribbon.SetSelectedTab (1);
}
/// <summary>Activate ribbon tab 2 action to execute on kex gesture binding.</summary>
/// <param name="parameter">Data used by the command. If the command does not require data
/// to be passed, this object can be set to null.<see cref="System.Object"/></param>
public void ActivateRibbonTab2_Action (object parameter)
{
    ribbon.SetSelectedTab (2);
}

...

为了指示 GUI 元素的键盘快捷键,应使用标记语法。

...

    XrwRibbonTab dialogtestTab = XrwRibbonTab.NewRibbonTabGadget
        (ribbon, "<markup><u>D</u>ialog test</markup>");

...

    XrwRibbonTab radiotoggletestTab  = XrwRibbonTab.NewRibbonTabGadget
        (ribbon, "<markup><u>R</u>adio & toggle test</markup>");

...

    XrwRibbonTab splitTab  = XrwRibbonTab.NewRibbonTabGadget
        (ribbon, "<markup><u>S</u>plit test</markup>");

...

结果如下所示。功能区选项卡可以通过组合键 [Alt]+[d]、[Alt]+[r] 或 [Alt]+[s] 进行选择。

事件管理策略比较

此表更仔细地研究了指针事件和触发的操作。一致的行为对于用户接受度来说是一个非常重要的事实。不同的行为以红色突出显示。

操作 Windows 8.1 GTK 2 清晰外观 Xrw Xrw 备注
打开菜单
下拉
按钮 1 按下
在菜单按钮上
任何按钮按下
在菜单按钮上
任何按钮按下
在菜单按钮上
 
关闭菜单
下拉
延迟按钮 1
在相应的菜单按钮上按下
菜单按钮
延迟 任何 按钮
释放 在相应的
在菜单按钮上
延迟 任何 按钮
在相应的菜单按钮上按下
菜单按钮
按下更
常见
关闭菜单
下拉
在菜单外部的任何按钮按下
在菜单外部的任何按钮按下
下拉
任何按钮 释放
在菜单外部的任何按钮按下
下拉
在菜单外部的任何按钮按下
在菜单外部的任何按钮按下
下拉
按下更
常见
选择菜单项 按钮 1 释放
在菜单项上
任何按钮释放
在菜单项上
任何按钮释放
在菜单项上
目前没有
上下文帮助
打开组合框 按钮 1 按下
在组合框上
按钮 1 按下
在组合框上
任何按钮按下
在组合框上
 
关闭组合框 延迟按钮 1
在组合框下拉列表上按下
框下拉列表
延迟按钮 1
释放 在组合
框下拉列表
延迟 任何 按钮
在组合框下拉列表上按下
框下拉列表
按下更
常见
关闭组合框 在菜单外部的任何按钮按下
组合框外部
框下拉列表
任何按钮 释放
组合框外部
框下拉列表
在菜单外部的任何按钮按下
组合框外部
框下拉列表
按下更
常见
选择组合框
按钮 1 释放
在组合框项目上
按钮 1 释放
在组合框项目上
按钮 1 释放
在组合框项目上
目前没有
上下文帮助
打开功能区的
应用程序菜单
按钮 1 按下
在菜单按钮上
- 任何按钮按下
在菜单按钮上
 
关闭功能区的
应用程序菜单
延迟按钮 1
在相应的菜单按钮上按下
菜单按钮
- 延迟 任何 按钮
在相应的菜单按钮上按下
菜单按钮
 
关闭功能区的
应用程序菜单
在菜单外部的任何按钮按下
在菜单外部的任何按钮按下
下拉
- 在菜单外部的任何按钮按下
在菜单外部的任何按钮按下
下拉
 
选择功能区的
应用程序菜单
按钮 1 释放
在菜单项上
- 任何按钮释放
在菜单项上
目前没有
上下文帮助
打开功能区的
拆分按钮菜单
按钮 1 按下
在拆分按钮上
- 任何按钮按下
在拆分按钮上
 
关闭功能区的
拆分按钮菜单
延迟按钮 1
在相应的菜单按钮上按下
拆分按钮
- 延迟 任何 按钮
在相应的菜单按钮上按下
拆分按钮
 
关闭功能区的
拆分按钮菜单
在菜单外部的任何按钮按下
拆分按钮下拉框外部
按钮下拉
- 在菜单外部的任何按钮按下
拆分按钮下拉框外部
按钮下拉
 
选择功能区的
拆分按钮菜单
按钮 1 释放
在菜单项上
- 任何按钮按下 目前没有
上下文帮助
选项卡选择 按钮 1 按下 按钮 1 按下 任何按钮按下  
功能区选择 按钮 1 按下 - 任何按钮按下  
就地文本
编辑器调用
延迟按钮 1
项目选择后按下
通过所选项目
的上下文菜单
的上下文菜单
延迟  任何 按钮
项目选择后按下
通过所选项目
延迟按钮
按下更
常见
就地文本
编辑器离开
在菜单外部的任何按钮按下
就地编辑器外部
就地编辑器
在菜单外部的任何按钮按下
就地编辑器外部
就地编辑器
在菜单外部的任何按钮按下
就地编辑器外部
就地编辑器
 

剪贴板使用

目前唯一支持的剪贴板数据类型是 STRING(原子 _XA_STRING)。如果需要支持其他剪贴板数据类型,请参阅 XrwApplicationShell 的属性区域以获取更多原子定义。

要通过剪贴板复制或粘贴文本数据(在应用程序之间),任何小部件(而非小工具)都可以通过 XrwApplicationShell.ProvideClipboardText() 提供数据或通过 XrwApplicationShell.RequestClipboardText() 请求数据。XrwText 小部件的剪贴板支持如下所示

{
    ...
    // XrwText widget: Provide text data (_selectionStart and _selectionEnd are character positions).
    XrwApplicationShell app = this.ApplicationShell;
    if (_selectionStart != _selectionEnd && app != null)
    {
        SetCopyBuffer ();
        app.ProvideClipboardText (_surface, e.Event.time);
    }
    ...
}

...

{
    ...
    // XrwText widget: Request test data.
    XrwApplicationShell app = this.ApplicationShell;
    if (app != null)
    {
        app.RequestClipboardText (_surface, e.Event.time);
    }
    ...
}

...

{
    ...
    // XrwApplicationShell: What happens inside the application's event loop to inject result?
    s = Marshal.PtrToStringAuto (data);
    if (target as XrwText != null)
    {
        (target as XrwText).Paste (s);
    }
    ...
}

除了“传统 X11 兼容”方法 XrwApplicationShell.ProvideClipboardText()XrwApplicationShell.RequestClipboardText() 之外,现在还支持具有 Windows 兼容名称但不同参数列表的方法,以提供替代的“方便的 MS Windows 类似”行为。为此,“传统 X11 兼容”功能由 System.Windows.ClipboardSetText()GetText() 方法封装。

注意:即使“方便的 MS Windows 类似”行为的命名空间 System.Windows.Clipboard 暗示了一个应用程序独立的剪贴板,X11 也不提供应用程序独立的剪贴板——它必须由窗口管理器来实现才能良好运行,而不是由应用程序或应用程序编程框架。因此,成功的复制/粘贴操作始终要求提供者小部件和接收者小部件都存活。

最后,由于 X11 系统上剪贴板数据的分布式存储(任何小部件都可以是剪贴板数据提供者)和异步处理(应用程序间消息处理)(与集中/单片 Windows 剪贴板系统相比),必须为 GetText() 提供一个委托以将结果注入请求者。

{
    ...
    // XAML TextBox control: Provide text data.
    XrwApplicationShell app = this.ApplicationShell;
    if (_selectionStart != _selectionEnd && app != null)
    {
        Clipboard.SetText (Entry.Text);
    }

...

    // XAML TextBox control: Request test data.
    XrwApplicationShell app = this.ApplicationShell;
    if (app != null)
    {
        // Besause of the asynchronous processing of clipboard data(inter-application
        // message processing) a delegate to inject the result must be provided.
        Clipboard.GetText (this.ProcessClipboardPasteToEntry);
    }
    ...
}

...

/// <summary>Handle the ClipboardGetResult event.</summary>
/// <param name="result">The clipboard get text result.<see cref="System.Object"/></param>
private void ProcessClipboardPasteToEntry (object result)
{
    if (result != null)
    {
        Entry.Text = result.ToString ();
    }
}

...

{
    ...
    // XrwApplicationShell: What happens inside the application's event loop to inject result?
    s = Marshal.PtrToStringAuto (data);
    if (target as XrwApplicationShell != null)
    {
        System.Windows.ClipboardGetResultDelegate clipboardGetResult =
            System.Windows.Clipboard.ClipboardGetResult;
        if (clipboardGetResult != null)
            clipboardGetResult (s);
    }
    ...
}

历史

本文已从文章 编程 Roma 小部件集 (C# X11) - 零依赖 GUI 应用程序框架 - 第 1 部分,基础 拆分出来,版本为 Roma 小部件集的第四个公开版本,版本 ,发布于 2014 年 5 月 13 日。
Roma 小部件集的第五个公开版本是版本 ,发布于 2014 年 8 月 15 日。
Roma 小部件集的第六个公开版本是版本 ,发布于 2014 年 10 月 5 日。
Roma 小部件集的第七个公开版本是版本 ,发布于 2014 年 12 月 14 日。
Roma 小部件集的第八个公开版本是版本 ,发布于 2015 年 3 月 8 日。
Roma 小部件集的第八个公开版本是版本 ,发布于 2015 年 10 月 21 日。

© . All rights reserved.