Introduction
This article is a hands-on tutorial, how to write a MVVM (Model View ViewModel) design pattern based X11 dialog interface (simple GUI) application with XAML using the Roma Widget Set (Xrw). The Roma Widget Set is a zero dependency GUI application framework for X11 (it requires only assemblies of the free Mono standard installation and libraries of the free X11 distribution; it doesn't particularly require GNOME, KDE or commercial libraries) and is implemented entirely in C#.
As far as i know, this (utilizing the Xrw) is the first attempt to use XAML for X11 application development after the abandonment of Moonlight.
Neither the Roma Widget Set nor the XAML implementation are complete. This sample application is intended as a 'proof of concept' and checks out if and how it is possible bo create MVVM design pattern based X11 application with XAML.
The next article about XAML using the Roma Widget Set on X11 will be Writing a XAML ribbon application for X11.
Background
Motivation
Although System.Windows.Forms
applications are still alife, more and more existing application switch over to XAML and the majority of new applications are built with XAML now on the Microsoft® Windows® platform.
Even though XAML introduces new problems to the developer like
- redundant and verbose language syntax,
- low efficiency causes higher processing costs,
- designed for hierarchical models, not for relational models, it requires extra effort to express overlapping (non-hierarchical) relationships,
- usage of namespaces makes handling and interpretion difficult and
- critical ambiguities of a supposedly simple language
it has - among a lot of others - three very serious advantages. XAML
- is platform-independent,
- is plain text and easily to maintain and
- forces a clear separation and loose coupling of GUI and business logic.
Since a good documentation can minimize the effect of the drawbacks, the advantages are even more serious. Why not use XAML for X11 application development too and avoid rather frustrating initial experience (because of the fairly steep learning curve) with a good tutorial? O.K. - let's go!
Concept
To introduce XAML to X11 application development some challenges have to be mastered:
- The GUI application framework must support dynamic creation of controls and other framework elements.
- The XAML syntax should be as near to the Microsoft® original as reasonable. This shall
- allow the transfer of acquired knowledge and written XAML code between X11 and Windows® and
- support the separation of GUI and business logic to write platform independent code.
- Framework elements, defined within XAML, must also be accessible through C# code. This is required to enable the property change notification mechanism for notification from ViewModel to View.
- The GUI application framework must support appropriate reflection methods to enable the property change notification mechnism for notification from View to ViewModel.
The approaches to master these challenges are:
- An XAML wrapper around the Roma Widget Set. XAML wrapper and Xrw are written entirely in C# and can take usage of the dynamic language features.
- A custom XAML interpreter. This enables easy adopting to the desired syntax and functionality as near to the Microsoft® original as reasonable.
- An individually created XAML preprocessor, that dynamically generates that one part of partial classes, that is associated with the definitions made within XAML.
- The implementation of suffisticated generic reflection methods for the XAML wrapper around Xrw.
To evaluate these approaches a basic XAML based dialog application should be implemented. The sample application is written with Mono Develop in C#. It is based upon Mono Develop's fetaure to run user defined commands before code compilation to run the XAML preprocessor.
Focus
This article shall demonstrate that
- a window with some controls can be defined with XAML and
- click events can be connected to buttons with XAML.
Using the code
The sample application was written with Mono Develop 2.4.1 for Mono 2.8.1 on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop. Neither the port to any older nor to any newer version should be a problem. The sample application's solution consists of two projects (the complete sources are provided for download):
- XamlDialogApp contains the source code of the sample application.
- XamlPreprocessor contains the source code of the XAML preprocessor.
The sample application is also tested with Mono Develop 3.0.6 for Mono 3.0.4 on OPEN SUSE 12.3 Linux 64 bit DE and GNOME desktop, IceWM, TWM und Xfce.
The only difference between the 32 bit and the 64 bit solution is the definition of some X11 specific data types, as already described in the Programming Xlib with Mono develop -Part 1: Low level (proof of concept) article.
The Xlib/X11 window handling is based on the X11Wrapper assembly version 0.7, that defines the function prototypes, structures and types for Xlib/X11 calls to the libX11.so. This assembly has been developed for the Programming Xlib with Mono Develop - Part 1: Low-level (proof of concept) project and has been advanced during the Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Basics project.
The GUI framework is based on the Xrw assembly version 0.7, that defines the widgets/gadgets and its wrapper classes used within the XAML code (that should be as near to the Microsoft® original as reasonable). This assembly has been developed during the Programming the Roma Widget Set (C# X11) - a zero dependency GUI application framework - Basics project.
Advice: To use the class library documentation shortcut (F1) from MonoDevelop, the "mono-tools" package has to be installed.
The image shows the sample application with XrwTheme.GeneralStyle.WinMidori
.
The sample application's business functionality consists of
- the decoding of Base64 coded text (*.txt) to binary image files (*.decoded.bmp),
- the encoding of binary image files (*.bmp) to Base64 coded text (*.encoded.txt) and
- the opening of the recently encoded file by the system's default text editor
to demonstrate a practical use case for a dialog interface application.
The default image format (that means the default file name extension) is BMP. Other file formats should work as well, but require to change the file name extension afterwards.
The Windows version, developed in parrallel to realize XAML code as near to the Microsoft® original as reasonable, uses System.Convert.FromBase64String()
and System.Convert.ToBase64String()
. Because of the buggy implementation of these methods in Mono, the Linux/Unix versions of this sample application use X11.AlternativeBase64.Decode()
and X11.AlternativeBase64.Encode()
instead.
Step by step instruction
Project setup
First of all we need a new empty C# project. No additional features are required. MONO / .NET 3.5 or higher is recommended as runtime version.
The project references must include the standard packages System
, System.Core
, System.Drawing
, System.Xml
and the assemblies (or projects) X11Wrapper
and Xrw
. There are seven initial files required for the project, that can be named arbitary. To be as near to the Microsoft® original as reasonable, recommended are
App.xaml
for the application's class XAML definition, App.xaml.cs
for the application's class (manual) implementation / code behind, MainModel.cs
for the MVVM data Model of the main view, MainView.xaml
for the (dialog window) main view's class XAML definition, MainView.xaml.cs
for the (dialog window) main view's class (manual) implementation / code behind, MainViewModel.cs
for the MVVM ViewModel of the main view and - the application's icon file.
And finally the XAML preprocessor must be included into the project. This is done by a user defined command, executed before compilation.
The preprocessor is part of the solution and situated at '../XamlPreprozessor/bin/Debug/XamlPreprozessor.exe' relative to the XamlDialogApp project folder. The location can be changed without any drawback. The current working directory of the command to execute must be set to the XamlDialogApp project folder, otherwise the preprocessor might examine the wrong folder.
Currently the preprocessor doesn't examine sub-folders. But this is O.K. for this sample.
Application file content
The XAML (App.xaml)
The first XAML file to take a look at is App.xaml
.
<Application x:Class="XamlDialogApp.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainView.xaml"
Style="WinMidori">
<!-- Supported styles are: WinClassic, WinLuna, WinRoyale, WinMidori, Gtk2Clearlooks -->
<Application.Resources>
</Application.Resources>
</Application>
Except the attribute Style="WinLuna"
, the XAML code is fully Microsoft® compatible. Alternatively the style can be set within App.xaml.cs
to avoid this incompatibility.
The Application
will be defined by:
- The root node is named
Application
. This node is mandatory. The XAML processing relies on the node name Application
. - The
x:Class
attribute defines the namespace name (XamlDialogApp
) and class name (App
) of the application. This attribute is mandatory. It is strictly recommended to define namespace and class. The XAML processing relies on the attribute name x:Class
. The attribute name x:Class
is a XAML extension, defined within the http://schemas.microsoft.com/winfx/2006/xaml
namespace. - The
xmlns
and xmlns:x
attributes are currently not evaluated and part of the XAML code for Microsoft® compatibility only. - The
StartupUri
attribute defines the XAML file URI of the startup UI, that is MainView.xaml
for this sample. This attribute is mandatory. The XAML processing relies on the attribute name StartupUri
. - The
Style
attribute defines the theme, Xrw shall use to display the UI. This attribute is optional. - The
Application.Resources
node is currently not evaluated. This attribute is optional.
The code behind (App.xaml.cs)
The corresponding C# code behind file is App.xaml.cs
.
using System;
using Xrw;
using XrwXAML;
namespace XamlDialogApp
{
public partial class App : XrwXAML.Application
{
public static int Main ()
{
return Main (System.Reflection.Assembly.GetExecutingAssembly());
}
public App ()
{
this.InitializeComponent();
}
}
}
Mind the fact that the App
class is defined as partial class
! This is important, because the XAML preprocessor will generate the second part of the App
class based on the XAML code in App.xaml
.
Main view file content
The XAML (MainView.xaml)
The second XAML file to take a look at is MainView.xaml
.
<Window x:Class="XamlDialogApp.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:XamlDialogApp"
DataContext="src:MainViewModel"
Name="MainWindow" Title="XAML dialog application"
Width="650" Height="400" Icon="XrwIcon16.bmp">
<Window.Resources>
</Window.Resources>
<Grid Name="MainGrid">
<Grid.Resources>
<!-- <src:MainViewModel x:Key="mainViewDataSource" /> -->
</Grid.Resources>
<Grid.Datacontext>
<!-- <Binding Source="{StaticResource mainViewDataSource}"/> -->
</Grid.Datacontext>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="20"/>
<ColumnDefinition Width="100"/>
<ColumnDefinition Width="12"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="12"/>
<RowDefinition Height="28"/>
<RowDefinition Height="12"/>
<RowDefinition Height="28"/>
<RowDefinition Height="50"/>
<RowDefinition Height="28"/>
<RowDefinition Height="12"/>
<RowDefinition Height="28"/>
<RowDefinition Height="12"/>
<RowDefinition Height="68"/>
<RowDefinition Height="*"/>
<RowDefinition Height="12"/>
<RowDefinition Height="32"/>
</Grid.RowDefinitions>
<TextBox Name="DecodeSourcePath" Grid.Column="1" Grid.Row="1" Text=""
BorderThickness="2" IsEnabled="false" />
<TextBox Name="DecodeTargetPath" Grid.Column="1" Grid.Row="3" Text=""
BorderThickness="2" IsEnabled="false" />
<Button Name="Decode" Grid.Column="3" Grid.Row="1" Grid.RowSpan="3" Click="Decode_Click" >
<TextBlock Text="Decode Base64 to BMP/PNG" TextWrapping="Wrap"/>
</Button>
<TextBox Name="EncodeSourcePath" Grid.Column="1" Grid.Row="5" Text=""
BorderThickness="2" IsEnabled="false" />
<TextBox Name="EncodeTargetPath" Grid.Column="1" Grid.Row="7" Text=""
BorderThickness="2" IsEnabled="false" />
<Button Name="Encode" Grid.Column="3" Grid.Row="5" Grid.RowSpan="3" Click="Encode_Click" >
<TextBlock Text="Encode BMP/PNG to Base64" TextWrapping="Wrap"/>
</Button>
<Label Name="EncodeData" Content="" Grid.Column="1" Grid.Row="9" Grid.RowSpan="2"
BorderThickness="2" />
<Button Name="Open" Grid.Column="3" Grid.Row="9" Click="OpenResult_Click" >
<TextBlock Text="Open result in a text editor" TextWrapping="Wrap"/>
</Button>
<Label Name="State" Content="O.K." Grid.Column="0" Grid.Row="12" Grid.ColumnSpan="5"
BorderThickness="2" />
</Grid>
</Window>
The complete XAML code is fully Microsoft® compatible.
The Window
will be defined by:
- The root node is named
Window
. This node is mandatory. The XAML processing relies on the node name Window
. - The
x:Class
attribute defines the namespace name (XamlDialogApp
) and class name (MainView
) of the main view. This attribute is mandatory. It is strictly recommended to define namespace and class. The XAML processing relies on the attribute name x:Class
. The attribute name x:Class
is a XAML extension, defined within the http://schemas.microsoft.com/winfx/2006/xaml
namespace. - The
xmlns
and xmlns:x
attributes are currently not evaluated and part of the XAML code for Microsoft® compatibility only. - The
xmln
s:src
attribute defines the namespace of local source code resources. This attribute is recommended, or mandatory if any attribute values use the prrefix src:
as reference. The XAML processing relies on the suffix :src
. The syntax of the attribute value must be clr-namespace:
<namespace name>. The namespace, defined this way, can be referenced by attribute values via the prefix src:
. - The
DataContext
attribute defines the default/fallback data context, that is src:MainViewModel
for this sample. This attribute is optional. The DataContext
attribute is currently not evaluated. - The
Name
attribute defines the class instance name, that can be used to identify the class instance uniquely. This attribute is mandatory. The class instance name must not be the same as the class type name! - The
Title
attribute defines the window title. This attribute is optional. - The
Width
attribute defines the initial window width. This attribute is optional. - The
Height
attribute defines the initial window height. This attribute is optional. - The
Icon
attribute defines the window icon. This attribute is optional. - The
Window.Resources
node is currently not evaluated. This node is optional.
The Window
's root (widget geometry) manager control is a Grid
and will be defined by:
- The
Name
attribute defines the class instance name, that can be used to identify the class instance uniquely. This attribute is recommended, or mandatory if this class instance has to be accessible through C# code. - The
Grid.Resources
node is currently not evaluated. This node is optional. - The
Grid.Datacontext
node is currently not evaluated. This node is optional. - The
Grid.ColumnDefinitions
node joins the column definitions. This node is mandatory.
- The Grid.ColumnDefinition
node defines one grid column. This node is mandatory, at least once.
- The Width
attribute defines the grid column width. This attribute is mandatory. Positive integers are interpreted as width in pixel. The asterisk is interpreted as dynamic width. - The
Grid.RowDefinitions
node joins the row definitions. This node is mandatory.
- The Grid.RowDefinition
node defines one grid row. This node is mandatory, at least once.
- The Height
attribute defines the grid row height. This attribute is mandatory. Positive integers are interpreted as height in pixel. The asterisk is interpreted as dynamic height. - The
Grid
node can contain nodes, that define child controls, to be layed out by the grid.
The Grid
control contains TextBox
controls as children and they will be defined by:
- The
Name
attribute defines the class instance name, that can be used to identify the class instance uniquely. This attribute is recommended, or mandatory if this class instance has to be accessible through C# code. - The
Grid.Column
attribute defines the zero-based column index, the control has to be positioned inside a grid. The default value is 0. This attribute is recommended for a grid child, but mandatory for controls positioned not on column 0 inside a grid. The index must not exceed the available grid columns. - The
Grid.Row
attribute defines the zero-based row index, the control has to be positioned inside a grid. The default value is 0. This attribute is recommended for a grid child, but mandatory for controls positioned not on row 0 inside a grid. The index must not exceed the available grid rows. - The
Grid.ColumnSpan
attribute defines the number of columns, the control has to span inside a grid. This attribute is optional for a grid child, but mandatory for controls spanning multiple grid columns. To omit this attribute or to set it to "0" or "1" are equivalent. The span must not exceed the available grid columns. - The
Grid.RowSpan
attribute defines the number of rows, the control has to span inside a grid. This attribute is optional for a grid child, but mandatory for controls spanning multiple grid rows. To omit this attribute or to set it to "0" or "1" are equivalent. The span must not exceed the available grid rows. - The
Text
attribute defines the text to display. This attribute is optional. - The
BorderThickness
attribute defines the width of the control's border. This attribute is optional. - The
IsEnabled
attribute defines the sensitivity of the control. This attribute is optional. The default is true.
The Grid
control contains Button
controls as children and they will be defined by:
- The
Name
attribute defines the class instance name, that can be used to identify the class instance uniquely. This attribute is recommended, or mandatory if this class instance has to be accessible through C# code. - The
Grid.Column
attribute defines the zero-based column index, the control has to be positioned inside a grid. The default value is 0. This attribute is recommended for a grid child, but mandatory for controls positioned not on column 0 inside a grid. The index must not exceed the available grid columns. - The
Grid.Row
attribute defines the zero-based row index, the control has to be positioned inside a grid. The default value is 0. This attribute is recommended for a grid child, but mandatory for controls positioned not on row 0 inside a grid. The index must not exceed the available grid rows. - The
Grid.ColumnSpan
attribute defines the number of columns, the control has to span inside a grid. This attribute is optional for a grid child, but mandatory for controls spanning multiple grid columns. To omit this attribute or to set it to "0" or "1" are equivalent. The span must not exceed the available grid columns. - The
Grid.RowSpan
attribute defines the number of rows, the control has to span inside a grid. This attribute is optional for a grid child, but mandatory for controls spanning multiple grid rows. To omit this attribute or to set it to "0" or "1" are equivalent. The span must not exceed the available grid rows. - The
Content
attribute defines the text to display. This attribute is optional. A nestet TextBlock
node can be used alternatively. The Microsoft® original implementation of this attribute can't provide line breaks.
- The nested TextBlock
node defines text to display. This node is optional. It can provide line breaks.
- The Text
attribute defines the text string. This attribute is mandatory.
- The TextWrapping
attribute defines the text wrapping. This attribute is optional. - The
Click
attribute defines the click event delegate. This attribute is optional. Currently the delegate must be defined inside the class code of the Window
(code behind), this control is a child/grandchild of.
The Grid
control contains Label
controls as children and they will be defined by:
- The
Name
attribute defines the class instance name, that can be used to identify the class instance uniquely. This attribute is recommended, or mandatory if this class instance has to be accessible through C# code. - The
Grid.Column
attribute defines the zero-based column index, the control has to be positioned inside a grid. The default value is 0. This attribute is recommended for a grid child, but mandatory for controls positioned not on column 0 inside a grid. The index must not exceed the available grid columns. - The
Grid.Row
attribute defines the zero-based row index, the control has to be positioned inside a grid. The default value is 0. This attribute is recommended for a grid child, but mandatory for controls positioned not on row 0 inside a grid. The index must not exceed the available grid rows. - The
Grid.ColumnSpan
attribute defines the number of columns, the control has to span inside a grid. This attribute is optional for a grid child, but mandatory for controls spanning multiple grid columns. To omit this attribute or to set it to "0" or "1" are equivalent. The span must not exceed the available grid columns. - The
Grid.RowSpan
attribute defines the number of rows, the control has to span inside a grid. This attribute is optional for a grid child, but mandatory for controls spanning multiple grid rows. To omit this attribute or to set it to "0" or "1" are equivalent. The span must not exceed the available grid rows. - The
Content
attribute defines the text to display. This attribute is optional. - The
BorderThickness
attribute defines the width of the control's border. This attribute is optional.
The code behind (MainView.xaml.cs)
The corresponding C# code file is MainView.xaml.cs
. It contains the Click
delegates for all three Button
controls.
using System;
using System.IO;
using X11;
using Xrw;
using XrwXAML;
namespace XamlDialogApp
{
public partial class MainView : XrwXAML.Window
{
public MainView ()
: base (-1, -1)
{
}
private void Decode_Click(object sender, RoutedEventArgs e)
{
...
}
private void Encode_Click(object sender, RoutedEventArgs e)
{
...
}
private void OpenResult_Click(object sender, RoutedEventArgs e)
{
if (!string.IsNullOrEmpty (EncodeTargetPath.Text))
System.Diagnostics.Process.Start (EncodeTargetPath.Text);
}
public bool DecodeBase64FromStream(Stream sourceStream, Stream targetStream, out string message)
{
...
}
public bool EncodeBase64FromStream(Stream sourceStream, Stream targetStream, out string message)
{
...
}
}
}
Mind the fact that the MainView
class is defined as partial class
! This is important, because the XAML preprocessor will generate the second part of the MainView
class based on the XAML code in MainView.xaml
.
Preprocessor code generation
During the first compilation of the project, the preprocessor initially generates the partial class files App.generated.cs
from App.xaml
and MainView.generated.cs
from MainView.xml
. The Preprocessor writes log messages to the standard output, that can be inspected at the build output.
To go on with this sample, the initially generated files must be included into the project. Otherwise Mono Develop won't include the files into compilation and the generated parts of the partial classes App
and MainView
are missing. (That's why the very first compilation always generates errors.)
The generated *.generated.cs
files are incompatible to the Microsoft® ones:
- The Microsoft® ones are named
*.g.cs
. - The Microsoft® ones are hidden from the project( but the Mono ones mut be included into the project).
- The file content is completely different.
Now the functionality can grow step by step and the compilation produces executable assemblies.
Points of Interest
Is it possible to create MVVM design pattern based X11 application with XAML? YES it is! And it's fun. Hence this will not be the last article about XAML programming for X11.
Since 29. October 2014 the article Writing a XAML ribbon application for X11 continues the XAML topic.
Since 23. November 2014 the article Writing a XAML application for X11 with massive data binding and zero code continues the XAML topic.
Since 02. February 2015 the article Writing a XAML calculator application for X11 continues the XAML topic.
History
The first version is from 10. October 2014.
The first reviewed version from 29. October 2014.
The second reviewed version from 23. November 2014.
The third reviewed version from 19. February 2015.