Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Programming techniques






4.97/5 (13 votes)
How to develop Linux/Unix (X11) GUI applications in C# efficiently without dependencies to GUI frameworks like GTK or KDE. Description of simple widgets.
Introduction
This article contains the description of some programming techniques concerning the Roma Widget Set (Xrw) - eiter used to extend the widget set or to apply it for application programming. It has been created because the whole topic grows beyond 50 print pages, and i decided to split it into three parts with and into four parts with
. With
i moved the complete API description to a separate HTML documentation (that is part of the Xrw project) and restructured all four articles to make reading more entertaining and exciting:
The origin one: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Introduction. It contains a short explanation of the widget set's features. (Before
it was: ~ Basics. It contained general descriptions.) This atricle should always be the preferred starting point.
The first split-off: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Widget set. Due to the constantly growing of 'boring' API documentation, the API documentation has been moved with
to a separate HTML documentation (that is part of the Xrw project) and the article has been focused on a briefly introduction of all widgets. (Before
it was: ~ Intrinsic widgets. It contained the API reference description of intrinsic widgets only.)
This second split-off: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Programming techniques. Due to the constantly growing of 'boring' API documentation, the API documentation has been moved with
to a separate HTML documentation (that is part of the Xrw project) and this article has been focused on programming techniques. (Before
it was: ~ Simple widgets. It contained the API reference description of simple widgets only.)
The third split-off: Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - MVVM/XAML support. Due to the constantly growing of 'boring' API documentation, the API documentation has been moved with
to a separate HTML documentation (that is part of the Xrw project) and this article has been focused on introducing the MVVM/XAML support of the Xrw. (Before
it was: ~ Composite widgets. It contains the API reference description of composite widgets only.)
All features, described later on, can be marked as:
|
|
Theme support
The framework has a generic theme support and these predefined themes:
(inXrwTheme.GeneralStyle.WinClassic
and
named
XrwTheme.GeneralStyle.Win95
; looks like Windows 95/98/Me/2K and a little bit like Motif - classic gray 3D),
(loocks similar to Windows XP / MS Office 2007),XrwTheme.GeneralStyle.WinLuna
(loocks similar to Windows Vista/7 / MS Office 2010),
XrwTheme.GeneralStyle.WinRoyale
(loocks similar to Windows 8/8.1 / MS Office 2013) and
XrwTheme.GeneralStyle.WinMidori
(looks like Gnome 2.30.0 Clearlooks - typical GTK 2).XrwTheme.GeneralStyle.Gtk2Clearlooks
The theme support covers colors, geometries and images.
To design an uncommon GUI for a specific application, almost all widget properties, that are influenced by a theme, can also be overruled by individual values. Or a new, alternatively XrwTheme.GeneralStyle
can be created and applied.
Look & feel of XrwTheme.GeneralStyle.WinClassic
demonstrated with the "File selection" dialog.
Look & feel of XrwTheme.GeneralStyle.Gtk2Clearlooks
demonstrated with the "File selection" dialog.
See HTML cocumentation of XrwTheme
class for details and X11Graphic
class for supported stock items.
Font support
The default font of most Xfree86 installation is
"-misc-fixed-medium-r-semicondensed--13-*-*-*-*-*-*"
. Hence the XrwTheme
class predefines these default font specifications:
DefaultFontName = "-misc-fixed-medium-r-semicondensed--13-*-*-*-*-*-*";
DefaultItalicFontName = "-misc-fixed-medium-o-semicondensed--13-*-*-*-*-*-*";
DefaultBoldFontName = "-misc-fixed-bold-r-semicondensed--13-*-*-*-*-*-*";
These fonts are almost guaranteed to be available at every X server but they are monospace bitmap fonts and do not look very smart.
The image shows a sample -misc-fixed-
font output.
This version introduces convenience methods to change the predefined font specifications with user defined font specifications and ensures all widget's GC (graphics context) initialization with the
DefaultFontName
font specification.
TrySetDefaultFont
set theDefaultFontName
to the user defined font specification and returns true on success (font is available) or leaves theDefaultFontName
unchanged and returns false otherwise.TrySetDefaultItalicFont
set theDefaultItalicFontName
to the user defined font specification and returns true on success (font is available) or leaves theDefaultItalicFontName
unchanged and returns false otherwise.TrySetDefaultBoldFont
set theDefaultBoldFontName
to the user defined font specification and returns true on success (font is available) or leaves theDefaultBoldFontName
unchanged and returns false otherwise.
It is recommended to set user defined font specifications after the creation of the application shell before the widget hierarchy is build up.
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 ();
}
This version introduces -*-helvetica-
fonts for the XrwTheme
class predefinitions, because they look smarter than the -misc-fixed-
fonts.
The image shows a sample -*-helvetica-
font output.
Support for 16 Bit color model
The general approach to support 16 bit color model is to use an individual visual and colormap for the application. In this specific case it is based on a (virtually) 24 bit color model (hardware and X11 server have 24 bit color capabilities) while the X server is running in 16 bit color mode. There is no experience with a (physically) 16 bit color model (hardware and X11 server don't have 24 bit color capabilities).
The necessary code, especially XrwCore.InitializeApplicationShellWindow()
, XrwCore.InitializeTransientShellWindow()
and XrwCore.InitializeOverrideShellWindow()
has been prepared already by
. The following changes have been implemented with to provide support for this specific 16 bit color model case finally:
- Reduction of the stock icon's color depth to 15 bit (not necessarily needed, but recommended).
- Reduction of the application's icon color depth to 15 bit (required).
- Correction of
XrwApplicationFramework.SetWmShellIcon()
(see "Fixed with" No. 3).
- Correction of
(see "Fixed withX11Graphic.
CreateIndependentGraphicPixmap()" No. 4).
The /etc/X11/xorg.conf.d/50-screen.conf on my OPEN SUSE 11.3 Linux 32 bit EN has been modiefied like that, to test 16 bit color model support:
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
The ("X -configure" generated) /etc/X11/xorg.conf on my OPEN SUSE 12.3 Linux 64 bit DE has been modiefied like that, to test 16 bit color model support:
Section "Screen"
...
# DefaultDepth 32 Chrashing!
# DefaultDepth 24 # Running!
DefaultDepth 16 # Running!
# DefaultDepth 15 Running with Xfce only!
# DefaultDepth 8 # Running, but ugly!
EndSection
Internationalisation
Support for internationalized text output
There is a very good document about I18N. And Chapter 13.1 TWM -- usage of XFontSet instead of XFontStruct is the comprehensive guid for X11 application's internationalized text output implementation.
The first major change of the Roma Widget Set's internal architecture was to introduce the X11FontData
structure, that supports XFontSet
alternatively to XFontStruct
and to provide X11Surface.TextBoundings()
as single point for all text measurement as well as X11Surface.DrawString()
as single point for all text output.
The second major change of the internal architecture was to provide XFontSet
alternatively to XFontStruct
during font allocation. Depending on the I18N capabilities of the X server and the C runtime, the XrwApplicationShell
's UseFontSet
property is set to true
(or false
) and XFontSet
is used instead of XFontStruct
(or isn't). Font allocation is realized by XrwCore
's PrepareFont()
method.
The story of the subsequent methods is quickly recounted: After a check of the prerequisits it is tested if FontData
are to set to application's default fontset/font or current FontData
is already up-to-date. Elsewise the requested fontset (if XrwApplicationShell
's UseFontSet
property is true
) or font is loaded and assigned. To provide fast geometry calculation the fontsets/fonts maximum height, ascent and descent are provided with FontData
as well.
XrwApplicationShell
's font initialization:
/// <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
's font preparation:
/// <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
's font preparation:
/// <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)
...
Start loading the requested fontset. If requested fontSpecification
doesn't correspond to an existing fontset completely, make fontSpecification
more fuzzy step by step, until an existing fontset corresponds. This starts with stretch, continued with weight and slant. In case of no success, load the font server's fallback fontset.
...
// 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;
}
...
Report the finally loaded fontset and the missing character sets. Calculate some fontset attributes and create the 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;
}
...
Start loading the requested font, if fontset isn't supported. If requested fontSpecification
doesn't correspond to an existing font completely, make fontSpecification
more fuzzy step by step, until an existing font corresponds. This starts with stretch, continued with weight and slant. In case of no success, load the font server's fallback font.
...
// 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;
}
...
Report the finally loaded font. Create the 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;
}
}
Anatomy of an application or dialog window
Application windows are based on an
XrwApplicationShell
, dialog windows are based on an XrwDialogShell
. Both are derived from abstract XrwWmShell
, and XrwWmShell
is derived from XrwComposite
- the base class for containers, that manage an arbitary number of child widgets. XrwWmShell
provides interaction with the windows manager (move, resize, close, ... of a window). Since XrwComposite
has no integrated layout management, it is recommended to assign one XrwBox
, XrwGridForm
, XrwUniformGrid
or XrwDockPanel
child to each XrwApplicationShell
or XrwDialogShell
instance, that manages the layout of the shell's grandchildren.
Since XrwWmShell
derivatives set the XSetWindowAttributes
attribute bit_gravity
to NorthWestGravity
and the window's background color to XrwTheme.GeneralBackgroundColor
, the flickering effects during the redraw procedure (as concequence of ConfigureNotify
events - to observe on window resize operations) of a shell's layout manager widget/gadget are already minimized: Most of the flickering effects come from the black (undrawn) shell background and the time delay between shell background cleaning and manager widget/gadget redrawing.
There are some more reasons for flickering effects - see Chapter Specific aspects of event processing.
The sample code shows how to use a XrwBox
as manager gadget of a dialog 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.
...
}
A dialog window typically contains an action area with action buttons - e.g. Cancel and OK.
The next sample code shows how to create an action area, extracted from a dialog constructor.
{
...
// ---- 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.
...
}
This is what it looks like:
The button callbacks are connected to the Clicked
event.
/// <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);
}
Since this version introduces key gesture binding, the ultimate
Clicked
event processing isn't part of the event handler, but swaped out to the corresponding action method. This provides the feasibility to use the action methos a second time. Namely as keyboard input receiver using the Xrw.Utils.KeyGestureBinding
. Thereby the dialog can be closed with [Escape] key (that is equivalent to the dialog's Cancel button), [Enter] or [Return] key (that is equivalent to the dialog's OK button).
Dialogs are based on transient shells and often take over the (infinite) message loop processing from the application shell. To fully clean up such active dialogs by closing the application window, they must override the transient shell's
DefaultClose()
method to stop it's (infinite) message loop processing. Here is a closer look on this: The XrwTransientShell
implements these two message handler.
OnWmClose()
is called only if shell closing is invoked via the window decoration's close button. It's only purpose is to provide event forwarding to registered delegates. Even if it is virtual, additional functionality should be implemented via delegates rather than via overwriting. This, besides, promotes code reuse.
DefaultClose()
should always be called, no matter whether closing is invoked via the window decoration's close button, a widget/gadget inside the dialog (like the [OK] or [Cancel] button) or a keyboard shortcut (like [Return] or [Escape]). Derived classes can use the override to prepare return values and terminate their (infinite) message loop processing.
#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
A XrwTransientShell
derived class, e. g. XrwFileSelectionDialog
, should override the DefaultClose()
to prepare return values and terminate it's (infinite) message loop processing. And it should register and implement HandleDialogClose()
to provide a consistent behaviour on closing, no matter whether closing is invoked via the window decoration's close button, via a dialog's [Cancel] button or the [Escape] key shortcut.
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);
}
...
Usage of popup menus
Popup menus can be created very easily. These are the steps:
- Create a popup menu shell
XrwSimpleMenuShell
. - Add menu entries of
XrwSme
class to the menu shell and register the callbacks to the menu entries. - Force shell's geometry management.
- Create a menu button
XrmMenuButton
and add it to the parent composite. - Register the menu shell to the menu button.
There is no additional code to process pop up, layout, pop down or selection required.
The image shows a simple XrwMenuButton
with left and right (transparent multicolor) bitmap - including it's poped up menu, based on a XrwDialogShell
containing two XrwSme
with left and right (transparent multicolor) bitmap.
The sample code shows how to create the simple pop up menu, illustrated by the previous image.
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);
Closing an application Window
To provide a convenient API to the application developer, some things have to be prepared framework internally:
1. To clean up the XrwApplicationShell
, a primary shell's close delegate is registered:
// 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. Since the OnClose
delegate is implemented to invoke registered handler in reverse order, the clean up process always processes from derived classes back to the base class - and the base class handler must be the last besause it disconnects from X11 server.
/// <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);
}
This enables resource deallocation without memory leaks.
Usage of standard dialogs
Curently these standard dialogs are available:
XrwMessageBox
for a notification only dialog (no input except the choice between OK and Cancel).XrwFileSelectionDialog
for single file selection.XrwBitmapAndVectorFontSelectionDialog
to select a font using all X11 font information.
This is an image of the XrwMessageBox
with plain text and with markup text, introduced with .
This is an image of the XrwFileSelectionDialog
.
This is an image of the XrwBitmapAndVectorFontSelectionDialog
.
(This dialog is suitable for non-internationalized text output using XLoadFont
and XDrawString
or XDrawString16
but not for I18N text output using font set.)
Now these additional standard dialogs are available:
These are images of the XrwColorSelectionDialog
, using 16 predefined colors in 8 columns and 2 rows or 2 columns and 8 rows with color names.
Now this additional standard dialog is available:
These are images of the XrwColorChooseDialog
, using one notebook page for standard colors and one notebook page for custom colors.
Now this additional standard dialog is available:
This is an image of the XrwFontSelectionDialog
.
(This dialog is suitable for internationalized text output using font set, XCreateFontSet
and X
wcDrawString
.)
The creation of XrwFileSelectionDialog
, XrwBitmapAndVectorFontSelectionDialog
, XrwColorSelectionDialog
, XrwColorChoseDialog
and XrwFontSelectionDialog
is straight forward.
The sample code shows how to use the 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");
}
}
The next sample code shows how to use the 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");
}
}
The next sample code shows how to use the 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");
}
}
The next sample code shows how to use the 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");
}
}
The next sample code shows how to use the 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;
}
After the dialog instantiation this call is recommended:
dialog.SetMinimumSize(dialog.AssignedSize)
to prevent size underflow.
All dialogs calculate their initial size in a way that it is the smallest size where all controls are displayed properly. To set the initial size as the minimum size prevents the dialog from resizing to an unsuitable display.
Previous versions of Xrw recommended to call these methods after the dialog instantiation as well:
ApplicationFramework.SetWmShellIcon(dialog, APPICON_FILEPATH)
to set the shell icon.this.AddTransientShell(dialog)
to register the dialog to the application's transient shell list.
But these calls are not necessary any longer, because they are called framework internally now.
XrwFileSelectionDialog
, XrwBitmapAndVectorFontSelectionDialog
, XrwColorSelectionDialog
and XrwFontSelectionDialog
are implemented as application modal dialogs. Hence the call XrwDialogShell.DialogResult result = dialog.Run()
waits for the dialog end and the result
can be evaluated subsequently.
Now all standard dialogs support key gesture binding. Thereby the dialogs can be closed with [Escape] key (if dialog contains an Cancel button), [Enter] or [Return] key (that are equivalent to the dialog's OK button).
Specific aspects of event processing
Redraw performance issues and/or flickering
During the window resize process a plenty of ConfigureNotify
events are emitted by the windows manager. The application recalculates the layout of it's child widgets for every ConfigureNotify
event. New sizes in turn lead to corresponding Expose
events. Due to the asynchronous drawing model of X11 and - especially for complex layouts due to the layout recalculation time - redraw performance issues and/or flickering are the result.
To avoid this, Athena and Motif provide event compression flags with their wigets (compress_motion, compress_exposure, compress_enterleave). But this is no satisfying solution, because the events to compress must already be queued and immediate sequential. My tests with this approach didn't show a significant improvement.
The sample code shows a snippet of the XrwApplicationShell
's DoEvent()
method, that illustrates this event compression approach.
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;
}
...
}
The best way i've found to avoid redraw performance issues and/or flickering is to suspend every ConfigureNotify
event for some milli-seconds and to compress all ConfigureNotify
evens emmitted during this suspension. This causes a redrawing latency equal to the suspension interval, but the entire impression during a resizing process is much smoother.
The sample code shows a snippet of the XrwApplicationShell
's DoEvent()
method, that illustrates the event suspension approach.
/// <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;
}
...
}
The _compressConfigureBySuspend
interval can be adjusted to a certain use case to find the optimum between latency and flickering. A value of 0 completely suppresses event suspension.
The only drawback i've found is, that XrwPaned
doen't benefit from this approach.
Undoubtedly the best approach to avoid redraw performance issues and/or flickering is to use double buffering. This approach is saved for later use, because it breaks the Xrw zero dependency promise.
Key gesture binding
With this version key gesture binding is supported. It can be used to register global keyboard shortcuts to any
XrwShell
(typically application shell or dialog shell) targeting any GUI element. This technique can e. g. be used to improve the Accessibility of an application (or dialog). Typically the preferred targets of a key gesture binding are menus and ribbons. The sample code shows how to bind key gestures to a XrwRibbon
and select a specific 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);
}
...
To indicate the keyboard shortcuts for a GUI element, the markup syntax should be used.
...
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>");
...
This is what the result looks like. The ribbon tabs can be secected via key combinations [Alt]+[d], [Alt]+[r] or [Alt]+[s].
Event management policy in comparison
This table takes a closer look to the pointer events and actions that are invoked. Consistent behaviour is a very important fact for user acceptance. Different behaviour is highlighted in red.
Action | Windows 8.1 | GTK 2 clear looks | Xrw | Remark on Xrw |
open a menu drop-down |
button 1 press on menu button |
any button press on menu button |
any button press on menu button |
|
close a menu drop-down |
delayed button 1 press on appropriate menu button |
delayed any button release on appropriate on menu button |
delayed any button press on appropriate menu button |
press is more common |
close a menu drop-down |
any button press outside the menu drop-down |
any button release outside the menu drop-down |
any button press outside the menu drop-down |
press is more common |
select a menu item | button 1 release on menu item |
any button release on menu item |
any button release on menu item |
currently no context help |
open a combo box | button 1 press on combo box |
button 1 press on combo box |
any button press on combo box |
|
close a combo box | delayed button 1 press on combo box drop-down |
delayed button 1 release on combo box drop-down |
delayed any button press on combo box drop-down |
press is more common |
close a combo box | any button press outside the combo box drop-down |
any button release outside the combo box drop-down |
any button press outside the combo box drop-down |
press is more common |
select a combo box item |
button 1 release on combo box item |
button 1 release on combo box item |
button 1 release on combo box item |
currently no context help |
open the ribbon's application menu |
button 1 press on menu button |
- | any button press on menu button |
|
close the ribbon's application menu |
delayed button 1 press on appropriate menu button |
- | delayed any button press on appropriate menu button |
|
close the ribbon's application menu |
any button press outside the menu drop-down |
- | any button press outside the menu drop-down |
|
select a ribbon's application menu item |
button 1 release on menu item |
- | any button release on menu item |
currently no context help |
open the ribbon's split button menu |
button 1 press on split button |
- | any button press on split button |
|
close the ribbon's split button menu |
delayed button 1 press on appropriate split button |
- | delayed any button press on appropriate split button |
|
close the ribbon's split button menu |
any button press outside the split button drop-down |
- | any button press outside the split button drop-down |
|
select a ribbon's split button menu item |
button 1 release on menu item |
- | any button press | currently no context help |
tab selection | button 1 press | button 1 press | any button press | |
ribbon selection | button 1 press | - | any button press | |
in-place text editor invocation |
delayed button 1 press after item selection |
via context menu of the selected item |
delayed any button press after item selection |
delayed button press is more common |
in-place text editor leave |
any button press outside the in- place editor |
any button press outside the in- place editor |
any button press outside the in- place editor |
Clipboard usage
Currently the only supported clipboard data type ist STRING (atom
_XA_STRING
). See XrwApplicationShell
's attribute region for more atom definitions, if other clipboard data types are to support.
To copy or paste text data (between applications) via the clipboard any widget (but not a gadget) can provide data via XrwApplicationShell.ProvideClipboardText()
or request data via XrwApplicationShell.RequestClipboardText()
. An XrwText
widget's clipboard support looks like this:
{
...
// 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);
}
...
}
Beside the 'legacy X11 compatible' methods
XrwApplicationShell.ProvideClipboardText()
and XrwApplicationShell.RequestClipboardText()
, now methods of Windows compatible names but different argument lists support an alternatively 'convenient MS Windows similar' behaviour. To achieve this, the 'legacy X11 compatible' funftionality is wraped by the System.Windows.Clipboard
's methods SetText()
and GetText()
.
Attention: Even if the namespace System.Windows.Clipboard
of the 'convenient MS Windows similar' behaviour implies an application-independent clipboard, X11 doesn't provide an application-independent clipboard - it must be implemented by the windows manager to perform well, not by the application or application programming framework. Therefore a successful copy/paste operation always requires the provider widget and the receiver widget to be alife.
Finally besause of the distributed storage of clipboard data (any widget can be a clipboard data provider) and asynchronous processing of clipboard data (inter-application message processing) on X11 systems (compared to the centralized/monolithic Windows clipboard system) a delegate to inject the result into the requestor must be provided for 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);
}
...
}
History
This article has been split-off from the article Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Part 1, Basics with the fourth public version of the Roma widget set, version from 13. May 2014.
The fifth public version of the Roma widget set is version from 15. August 2014.
The sixth public version of the Roma widget set is version from 05. October 2014.
The seventh public version of the Roma widget set is version from 14. December 2014.
The eighth public version of the Roma widget set is version from 8. March 2015.
The eighth public version of the Roma widget set is version from 21. October 2015.