Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Writing a XAML application with geometry objects (shapes) for X11

5.00/5 (7 votes)
12 Jun 2015CPOL17 min read 25.5K   963  
Currently none of the big Linux/Unix (X11) GUI application frameworks (GTK+, KDE) support XAML based application development. The Moonlight project (including XAML support) was abandoned on May 29, 2012. This article reviews a XAML based application with WPF geometry objects (shapes).

Image 1 Download XamlGeometryApp_X11_32.zip Mono project including full source and executable
Image 2 Download XamlGeometryApp_X11_64.zip Mono project including full source and executable
Image 3 Download XamlGeometryApp_Win7.zip Visual Studio 2013 project including full source and executable

Introduction

This article is a case study, how to write a MVVM (Model View ViewModel) design pattern based X11 application (utilizing WPF geometry objects/shapes) 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#.

This article continues the works Writing a XAML dialog application for X11, Writing a XAML ribbon application for X11, Writing a XAML application for X11 with massive data binding and zero code and Writing a XAML calculator application for X11. 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 yet another 'proof of concept' and checks out if and how it is possible to create MVVM design pattern based X11 application with XAML.

Since this fifth attempt to use XAML for a X11 application development has been successful, further articles about XAML using the Roma Widget Set on X11 will follow certenly.

Background

The Motivation and the general Concept to use XAML for X11 application development are already explained in the Writing a XAML dialog application for X11 article.

Focus

While the first article Writing a XAML dialog application for X11 demonstrated, that with XAML

  • a window containing some controls can be defined and
  • click events can be connected to buttons,

the second article Writing a XAML ribbon application for X11 demonstrated, that with XAML

  • a window containing a ribbon command interface can be defined,
  • static window resources (this sample has a resource converter and a ModelView) can be defined,
  • a ModelView can be assigned to a control's data context as a static resource,
  • a resource converter can be applied to a control's property as a static resource,
  • commands can be bound to buttons via the "RelayCommand" approach,
  • control properties can be bound to the data context and
  • controls can be updated via the INotifyPropertyChanged interface,

the third article  Writing a XAML application for X11 with massive data binding and zero code demonstrated, that with XAML

  • massive data binding can provide a useful functionality and
  • an application with zero code behind can be defined,

the fourth article Writing a XAML calculator application for X11 demonstrated that with XAML

  • simple menus can be designed easily,
  • clipboard text exchange can be achieved and
  • data validation shows input errors, utilizing the built-in converter exception,

and the updated version of the fourth article demonstrated as well

  • custom binding validation and
  • keyboard shortcut binding,

this article shall demonstrate that with XAML

  • simple WPF geometry objects (shapes) like line, arc, rectangle, rounded rectangle, ellipse, polyline, polygon and path can be created to be drawn on a canvas and
  • can be deleted or manipulated (fill, position, rotation).

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):

  • XamlGeometryApp 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.9 (library version 0.9 preview included in this sample project), 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.9 (library version 0.9 preview included in this sample project), 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.

All images show the sample application in the same state: Containing all initial geometries without any new geometries (from the left toolbar) or any manipulations (from the right toolbar).

The first image shows the sample application on OPEN SUSE 11.3 Linux 32 bit EN and GNOME desktop.

Image 4

The second image shows the sample application on OPEN SUSE 12.3 Linux 64 bit DE and Xfce.

Image 5

The third image shows the sample application on Windows® 7 64 Bit Edition.

Image 6

The sample application provides a simple canvas to draw geometries on. All intrinsic WPF geometries (System.Windows.Shapes.Line, (rounded) System.Windows.Shapes.Rectangle, System.Windows.Shapes.Ellipse, System.Windows.Shapes.Polyline, System.Windows.Shapes.Polygon and System.Windows.Shapes.Path) are testet. There is no tool button on the left toolbar to create a new path, but one initial path geometry (at the right bottom of the canvas) demonstrates the capabilities of paths.

In addition to the intrinsic WPF geometries, the self-made System.Windows.Shapes.Arc class completes the available geometries.

The window utilizes the System.Windows.Controls.DockPanel as it's root layout manager. A simple menu bar with the one-level menus File and ? is docked to the top and a status bar is docked to the bottom; both use the complete available width. One System.Windows.Controls.Grid, docked to the left, organizes the tool buttons to create new geometries. Another System.Windows.Controls.Grid, docked to the right, organizes the tool buttons to manipulate the currently selected geometry. The center (dock position fill) contains a System.Windows.Controls.Cancas, surrounded by a System.Windows.Controls.Border to enable geometry object (shape) clipping. (The System.Windows.Controls.Cancas doesn't provide clipping at all by design, while the XrwXAML.Canvas wraps an Xlib/X11 widget with own window and provides clipping by default.) The canvas of this sample application provides single selection of geometries by left mouse button click.

All tool buttons have callbacks registered to their Click property to achieve the functionality (the Click property reduces the code and has no disadvantages for this simple project compared to the Command property and "RelayCommand" approach, discussed in the Writing a XAML ribbon application for X11 article).

The only functional difference between the X11 and Windows 7 versions of the sample application is the additional hatch brush support on X11 for "Re-fill" tool button (on the right toolbar). This is because some hatch bitmaps are integrated on X11 (X11BrushInfo.HatchType.*) while hatch brush definition on Windows 7 requires extra effort.

Walk through

The Project setup, Application file context (except the theme) and Preporocessor code generation steps are exactly the same as in Writing a XAML dialog application for X11. Please refer to this article, if a new solution shall be created from scratch.

Main view file context

The XAML (MainView.xaml)

First the XAML file with omitted canvas content.

XML
<Window         x:Class="XamlGeometryApp.MainView"
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                xmlns:src="clr-namespace:XamlGeometryApp"
                Name="MainWindow" Title="XAML geometry application"
                Width="510" Height="380" Icon="XrwIcon16.bmp">
    <!-- ATTENTION: To set an application icon, a Resources.resx file must
                    be created for the project on the windows platform. -->
    <Window.Resources>
        <ResourceDictionary>
            <src:MainWindowViewModel x:Key="MainViewModel" />
            <!-- MainWindowViewModel : ViewModelCore<T>.ctr() automatically registeres
                 itself as the initial Window.DataContext. -->
        </ResourceDictionary>
    </Window.Resources>
    <DockPanel Name="MainDockPanel" Background="#E8E8E8"
               DataContext="{StaticResource MainViewModel}">
        <Menu Name="MainMenu" DockPanel.Dock="Top" >
            <MenuItem Name="MenuItemFile"  Header="_File ">
                <MenuItem Name="MenuItemFileEdit"   Header="Exit"
                          Click="MenuItemFileExit_Click" />
            </MenuItem>
            <MenuItem Name="MenuItemQmark" Header="_?">
                <MenuItem Name="MenuItemQmarkHelp"  Header="Help"
                          Click="MenuItemQmarkHelp_Click" />
                <MenuItem Name="MenuItemQmarkAbout" Header="About"
                          Click="MenuItemQmarkAbout_Click" />
            </MenuItem>
        </Menu>
        <Label Name="StateBar" Content="Ready." HorizontalContentAlignment="Left"
               DockPanel.Dock="Bottom"/>        
        <Grid Name="LeftGrid" Background="#E8E8E8" DockPanel.Dock="Left">
            <Grid.Resources>
                <!-- <src:MainViewModel x:Key="mainViewDataSource" /> -->
            </Grid.Resources>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="70" />
                <!-- Right padding. -->
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
            </Grid.RowDefinitions>
            <Button Name="ButtonNewLine" Grid.Row="0" Click="ToolNewLine_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New Line" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonNewArc" Grid.Row="1" Click="ToolNewArc_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New Arc" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonNewRect" Grid.Row="2" Click="ToolNewRect_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New Rect" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonNewRRect" Grid.Row="3" Click="ToolNewRRect_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New RRect" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonNewEllipse" Grid.Row="4" Click="ToolNewEllipse_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New Ellipse" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonNewPolyline" Grid.Row="5" Click="ToolNewPolyline_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New Polyline" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonNewPolygon" Grid.Row="6" Click="ToolNewPolygon_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="New Polygon" TextWrapping="Wrap"/>
            </Button>
        </Grid>
        <Grid Name="RightGrid" Background="#E8E8E8" DockPanel.Dock="Right" Margin="0" >
            <Grid.Resources>
                <!-- <src:MainViewModel x:Key="mainViewDataSource" /> -->
            </Grid.Resources>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="80" />
                <!-- Right padding. -->
            </Grid.ColumnDefinitions>
            <Grid.RowDefinitions>
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.0*" />
                <RowDefinition Height="1.5*" />
                <RowDefinition Height="1.5*" />
                <RowDefinition Height="1.5*" />
                <RowDefinition Height="1.5*" />
                <RowDefinition Height="1.5*" />
                <RowDefinition Height="1.5*" />
            </Grid.RowDefinitions>
            <Button Name="ButtonDelete" Grid.Row="0" Click="ToolDelete_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Delete" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonRefill" Grid.Row="1" Click="ToolRefill_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Re-fill" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonMoveUp" Grid.Row="2" Click="ToolModeUp_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Move up" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonMoveDown" Grid.Row="3" Click="ToolModeDown_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Move down" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonMoveLeft" Grid.Row="4" Click="ToolMoveLeft_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Move left" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonMoveRight" Grid.Row="5" Click="ToolMoveRight_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Move right" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonRotateClock" Grid.Row="6" Click="ToolRotateClock_Click"
                    BorderThickness="2" Margin="2" >
                <TextBlock Text="Rotate clock" TextWrapping="Wrap"/>
            </Button>
            <Button Name="ButtonRotateCounterclock" Grid.Row="7"
                    Click="ToolRotateCounterclock_Click" BorderThickness="2" Margin="2" >
                <TextBlock Text="Rotate counterclock" TextWrapping="Wrap"/>
            </Button>
        </Grid>
        <Border Name="CanvasClipBorderForWindowsOnly" ClipToBounds="True" >
            <Canvas Name="MainCanvas" Background="#ffff88" MouseUp="MainCanvas_MouseUp">
                <!-- ... -->
            </Canvas>
        </Border>
    </DockPanel>
</Window>

The complete XAML code is fully Microsoft® compatible.

Compared to Writing a XAML calculator application for X11 and previous articles, only the enhancments and differences will be discussed.

The DockPanel 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 Background attribute defines the background color. This attribute is optional. The syntax of the attribute is a hexadecimal value #AARRGGBB or #RRGGBB with AA for alpha, RR for red, GG for green and BB for blue. Currently the alpha value is not implemented.
  • The DataContext attribute defines the default/fallback data context, that is StaticResource MainViewModel (which referres to the Window.Resources dictionary) for this sample. This attribute is optional. The DataContext attribute is currently not evaluated.

The DockPanel provides the child constraint DockPanel.Dock to position children to it's Top, Left, Right, Bottom or Fill (aka center) position.

The Border 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 ClipToBounds attribute defines the clipping behaviour. This attribute is optional, but required by this sample to clip the content of the enclosed canvas.

The Canvas 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 Background attribute defines the background color. This attribute is optional. The syntax of the attribute is a hexadecimal value #AARRGGBB or #RRGGBB with AA for alpha, RR for red, GG for green and BB for blue. Currently the alpha value is not implemented.
  • The MouseDown attribute defines the mouse down event delegate. This attribute is optional. Currently the delegate must be defined inside the class code of the Window (code behind).
  • The MouseUp attribute defines the mouse up event delegate. This attribute is optional. Currently the delegate must be defined inside the class code of the Window (code behind).

Since the original Microsoft implementation of Canvas doesn't clip it's content at all, the clipping Border is required to prevent geometry objects (shapes) from covering controls outside the canvas. In contrast the X11 Canvas does always clip, because it is based on a widget with an own X11 window. To keep the sample code for Windows and X11 in sync, the X11 XAML code utilizes the Border around the Canvas as well.

The Canvas can contain any UIElement and place it at an absolute position. This sample is focused on System.Windows.Shapes.* children.

The Canvas provides the child constraint Canvas.Left to define the child's x coordinate offset and Canvas.Top to define the child's y coordinate offset from the canvas origin (left top corner).

Back to the canvas content now. This is the XAML file extract with the canvas content definitions, ommitted above.

XML
<!-- Image filled geometry ... -->
<Rectangle Name="ImageRect" Width="278" Height="227" Canvas.Left="57" Canvas.Top="10">
    <Rectangle.Fill>
        <ImageBrush ImageSource="Bridge.bmp"/>
    </Rectangle.Fill>
</Rectangle>
<!-- Simple geometries ... -->
<Line      Name="Line0"      Stroke="#5566AA" StrokeThickness="2"
           X1="0" Y1="0" X2="50" Y2="30" Canvas.Left="10" Canvas.Top="242" />
<Polyline  Name="Polyline0"  Stroke="#55CC66" StrokeThickness="2"
           Fill="#AAAA99" Points="0,0 10,10 20,0 30,10"
           Canvas.Left="236" Canvas.Top="304" />
<Polygon   Name="Polygon0"   Stroke="#996655" StrokeThickness="2"
           StrokeDashArray="3" Fill="#AAAA99" Points="0,0 20,20 40,0 60,20"
           Canvas.Left="203" Canvas.Top="250" />
<Rectangle Name="Rectangle0" Stroke="#556699" StrokeThickness="2"
           StrokeDashArray="3" Fill="#AAAA99" RadiusX="10" RadiusY="10" Width="50" Height="35"
           RenderTransformOrigin="2.1,14.467"
           Canvas.Left="148" Canvas.Top="242" />
<Ellipse   Name="Ellipse0"   Stroke="#556699" StrokeThickness="2"
           Fill="#AAAA99" Width="50" Height="35" RenderTransformOrigin="2.1,14.467"
           Canvas.Left="148" Canvas.Top="280" />
<Path      Name="Path0"      Stroke="#FF6699" StrokeThickness="2"
           Fill="#AAAA99" Data="M 0,0 L 50,25 L 50,75 L 0,50 Z Q 40,0 40,40"
           Canvas.Left="284" Canvas.Top="242" />
<!-- More path geometries ... -->
<Path Name="Path1" Fill="#c0dd89" Data="M 50,50 L 150,50 L 150,150 L 50,150 Z">
    <Path.RenderTransform>
        <TransformGroup>
            <RotateTransform CenterX="65" CenterY="65" Angle="45" />
            <ScaleTransform ScaleX="0.95" ScaleY="0.95" />
            <TranslateTransform  X="20" Y="20" />
        </TransformGroup>
    </Path.RenderTransform>
</Path>
<Path Name="Path2" Fill="#c01730" Data="M 160,160 L 260,160 L 260,260 L 160,260 Z"
      Canvas.Left="-74" Canvas.Top="-73">
    <Path.RenderTransform>
        <TransformGroup>
            <MatrixTransform Matrix="0.95,0.1, -0.1,0.95, -20,-20" />
        </TransformGroup>
    </Path.RenderTransform>
</Path>

The complete XAML code is fully Microsoft® compatible.

The Line 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 Stroke attribute defines the stroke brush. Currently only a SolidColorBrush is supported. This attribute is optional. The SolidColorBrush is defined by it's color. The syntax of the color is a hexadecimal value #AARRGGBB or #RRGGBB with AA for alpha, RR for red, GG for green and BB for blue. Currently the alpha value is not implemented.
  • The StrokeThickness attribute defines the width of the outline. This attribute is optional.
  • The StrokeDashArray attribute defines the lenght of dashes and gaps of the outline. This attribute is optional.
  • The X1 attribute defines the line's start point x coordinate. This attribute is mandatory.
  • The Y1 attribute defines the line's start point y coordinate. This attribute is mandatory.
  • The X2 attribute defines the line's end point x coordinate. This attribute is mandatory.
  • The Y2 attribute defines the line's end point y coordinate. This attribute is mandatory.
  • The RenderTransformOrigin attribute defines the offset to apply to a RenderTransform. There is no RenderTransform by default. The shaort hand syntax definition of a RenderTransform is not supported. The offset, defined by RenderTransformOrigin, is measured relative to the shape bounding's top and left and the amount is defined as a fraction of the shape bounding's width and height. Typical values range from 0.0 to 1.0. If no RenderTransformOrigin is defined, the RenderTransform is applied to the shape bounding's center. This is equivalent to a definition of RenderTransformOrigin="0.5,0.5".

The Polyline will be defined by the same attributes Name, Stroke, StrokeThickness, StrokeDashArray and RenderTransformOrigin as Line and in addition by:

  • The Points attribute defines the polyline's interpolation points as multiple pairs of x,y coordinates delimited by a space character. This attribute is mandatory.

The Polygon will be defined by the same attributes Name, Stroke, StrokeThickness, StrokeDashArray and RenderTransformOrigin as Line and in addition by:

  • The Points attribute defines the polygon's interpolation points as multiple pairs of x,y coordinates delimited by a space character. This attribute is mandatory. Polygons are always closed shapes. If first and last interpolation point coordinates differ, the polygon will be closed automatically.
  • The Fill attribute defines the fill brush. Currently only SolidColorBrush and ImageBrush are supported. This attribute is optional. If no Brush is defined, the shabe will not be filled. The SolidColorBrush is defined by it's color. The syntax of the attribute is a hexadecimal value #AARRGGBB or #RRGGBB with AA for alpha, RR for red, GG for green and BB for blue. Currently the alpha value is not implemented. The ImageBrush is defined by it's image source. The inline definition of an ImageBrush is not supported.

The Rectangle will be defined by the same attributes Name, Stroke, StrokeThickness, StrokeDashArrayRenderTransformOrigin and Fill as Polygon and in addition by:

  • The RadiusX attribute defines the rectangle's corner radius x coordinate. This attribute is optional. The default value is 0.
  • The RadiusY attribute defines the rectangle's corner radius y coordinate. This attribute is optional. The default value is 0.
  • The Width attribute defines the rectangle's x dimension. This attribute is mandatory.
  • The Height attribute defines the rectangle's y dimension. This attribute is mandatory.

The Ellipse will be defined by the same attributes Name, Stroke, StrokeThickness, StrokeDashArrayRenderTransformOrigin and Fill as Polygon and in addition by:

  • The Width attribute defines the ellipse's x diameter. This attribute is mandatory.
  • The Height attribute defines the ellipse's y diameter. This attribute is mandatory.

The Path will be defined by the same attributes Name, Stroke, StrokeThickness, StrokeDashArrayRenderTransformOrigin and Fill as Polygon and in addition by:

  • The Data attribute defines the path's sequence of move and draw commands. This attribute is mandatory. Currently supported move and draw commands are M x,y to move to indicated x,y coordinates,  L x,y to draw a line to indicated x,y coordinates, H x to draw a line horizontally to indicated x coordinate, V y to draw a line vertically to indicated y coordinate, Q x1,y1 x2,y2 to draw a quadratic bezier with control point x1,y1 coordinates to the x2,y2 coordinates, C x1,y1 x2,y2 x3,y3 to draw a cubic bezier with first control point x1,y1 coordinates and second control point x2,y2 coordinates to the x3,y3 coordinates and Z to draw a line to start point (close the path). Currently every move and draw command must be initiated by it's according letter (M, L, H, V, C, Q or Z).

Since the short hand syntax definition of a RenderTransform is not supported, the Line.RenderTransform, Polyline.RenderTransform, Polygon.RenderTransform, Rectangle.RenderTransform, Ellipse.RenderTransform and Path.RenderTransform will always be defined with long hand syntax and by a contained TransformGroup.

The TransformGroup will be defined by any combination of RotateTransform, ScaleTransform, TranslateTransform and MatrixTransform.

The RotateTransform will be defined by:

  • The CenterX attribute defines the rotation center x coordinate. This attribute is optional. The default value is 0.
  • The CenterY attribute defines the rotation center y coordinate. This attribute is optional. The default value is 0.
  • The Angle attribute defines the rotation angle in degrees clockwise. This attribute is mandatory.

The ScaleTransform will be defined by:

  • The ScaleX attribute defines the scale factor for the x coordinate. This attribute is optional. The default value is 0.
  • The ScaleY attribute defines the scale factor for the y coordinate. This attribute is optional. The default value is 0.

The TranslateTransform will be defined by:

  • The X attribute defines the translation offset for the x coordinate. This attribute is optional. The default value is 0.
  • The Y attribute defines the translation offset for the y coordinate. This attribute is optional. The default value is 0.

The MatrixTransform will be defined by:

  • The Matrix attribute defines the matrix components as value pairs delimited by a space character in the order m11,m12 m21,m22 offsetX,offsetY. This attribute is mandatory.

Limitations of the XAML syntax

X11/Xrw implementation:

  • The Stroke attribute currently supports SolidColorBrush only.
  • The StrokeDashArray attribute currently supports equal length of dashes and gaps only.
  • The Fill attribute currently supports SolidColorBrush and ImageBrush only.
  • The ImageBrush currently supports only image sources relative to the executing assembly.
  • The Canvas always clips it's content and currently supports System.Windows.Shapes.* children only.
  • The Brush color syntax doesn't currentlly support named colors and the alpha value is not implemented.

Windows implementation:

  • The MessageBox text parameter doesn't support markup.

The code behind (MainView.xaml.cs)

The corresponding C# code file of the main view is MainView.xaml.cs. It contains all the necessary attributes and properties to implement the geometry object (shape) manipulation as well as the menu and button callbacks.

The code of all left toolbar buttons callbacks (to create a new shape) is very similar. The code to create a new Arc shape shall be shown exemplarly (besause Arc is not an intrinsic WPF Shape but an self-made extension).

C#
/// <summary>Process the "NewArc" button click event.</summary>
/// <param name="sender">The event source.<see cref="System.Object"/></param>
/// <param name="e">The event data.<see cref="System.Windows.RoutedEventArgs"/></param>
private void ToolNewArc_Click(object sender, System.Windows.RoutedEventArgs e)
{
    System.Windows.Shapes.Arc newArc = new System.Windows.Shapes.Arc();
    newArc.Center = new Point(_randomizer.Next(60, 210), _randomizer.Next(60, 210));
    newArc.RadiusVector = new Vector(_randomizer.Next(10, 50), _randomizer.Next(10, 50));
    newArc.StartAngle = _randomizer.NextDouble() * 2 * Math.PI;
    newArc.EndAngle = newArc.StartAngle + _randomizer.NextDouble() * 2 * Math.PI;
    newArc.Stroke = new SolidColorBrush(RandomColor());
    newArc.StrokeThickness = _randomizer.Next(1, 5);
    newArc.Fill = RandomBrush();
    MainCanvas.Children.Add(newArc);
}

The Arc shows the typical approach to create a System.Windows.Shapes.* by code - using the default constructor, setting attribute by attribute and finally registering the shape to the canvas. The RandomColor() method creates a random color from random red, green and blue values. The RandomBrush() method creates a random SolidColorBrush or, on X11 only, a random ImageBrush with random hatch pattern.

The next code shows the Canvas mouse up handler that provides single selection of geomety objects (shapes) by left mouse button click.

C#
/// <summary>Process the "Canvas" mouse button up event.</summary>
/// <param name="sender">The event source.<see cref="System.Object"/></param>
/// <param name="e">The event data.<see cref="System.Windows.Input.MouseButtonEventArgs"/></param>
private void MainCanvas_MouseUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
    System.Windows.Point mousePos = e.GetPosition(MainCanvas);
    System.Windows.Media.PointHitTestResult hitTestResult =
        (System.Windows.Media.PointHitTestResult)System.Windows.Media.VisualTreeHelper.HitTest
            (MainCanvas, new System.Windows.Point(mousePos.X, mousePos.Y));
    UIElement selectedUIElement = null;
    bool selectionChanged = false;
    bool foundSelection = false;

    if (hitTestResult != null)
        selectedUIElement = (UIElement)hitTestResult.VisualHit;
    else
        selectedUIElement = null;

    for (int index = 0; index < MainCanvas.Children.Count; index++)
    {
        UIElement uiElement = MainCanvas.Children[index];
        if (uiElement is Image)
        {
            /* Currently not supported. */
        }
        if (uiElement is Shape)
        {
            System.Windows.Shapes.Shape shape = uiElement as System.Windows.Shapes.Shape;
            if (foundSelection == false && uiElement == selectedUIElement)
            {
                if (shape.GetSelected() != true)
                {
                    shape.SetOriginalStrokeBush(shape.Stroke);
                    shape.SetSelected(true);
                    selectionChanged = true;
                    shape.Stroke = SelectedControlBorderBrush;
                    shape.StrokeThickness += 2;
                }
                foundSelection = true;
            }
            else
            {
                if (shape.GetSelected() != false)
                {
                    shape.Stroke = shape.GetOriginalStrokeBush();
                    shape.SetSelected(false);
                    selectionChanged = true;
                    shape.StrokeThickness -= 2;
                }
            }
        }
    }
    if (selectionChanged != false)
    {
        MainCanvas.InvalidateVisual();
        OnCanvasChildSelectionChanged();
    }
}

The Canvas mouse up handler utilizes VisualTreeHelper.HitTest() and evaluates a PointHitTestResult to determine the new selected shape. Subsequently the old selection will be released and the new selection will be set. If old and new selection differ, selectionChanged is set and OnCanvasChildSelectionChanged() is called. This metod updates the IsEnabled property of all right toolbar buttons (to manipulate the currently selected shape) according to the shape type (not all shapes support all manipulations).

To distinguish whether a shape is selected or not, the System.Windows.Shapes.Shape class extension property SelectedProperty accessors GetSelected() and SetSelected() are used.

To highlight the new selected shape, it's Stroke property is set to MainView.xaml.cs's SelectedColorBorderBrus property and to unhighlight the old selected shape, it's Stroke property is set back to it's original Stroke brush. This is supported by System.Windows.Shapes.Shape class extension property OriginalStrokeBushProperty accessors GetOriginalStrokeBush() and SetOriginalStrokeBush().

Both extensions show a big advantage of the WPF dependency mechanism - extensions to existing classes can be achieved easyly without inheritance.

The code shows a shape manipulation callback from the right toolbar exemplarly.

C#
/// <summary>Process the "ModeUp" button click event.</summary>
/// <param name="sender">The event source.<see cref="System.Object"/></param>
/// <param name="e">The event data.<see cref="System.Windows.RoutedEventArgs"/></param>
private void ToolModeUp_Click(object sender, System.Windows.RoutedEventArgs e)
{
    for (int index = 0; index < MainCanvas.Children.Count; index++)
    {
        UIElement uiElement = MainCanvas.Children[index];
        if (uiElement is Image)
        {
            /* Currently not supported. */
        }
        if (uiElement is Shape)
        {
            Shape shape = uiElement as Shape;

            if (shape.GetSelected() == true)
            {
                Matrix m = shape.RenderTransform.Value;
                m.Translate(0, -10);
                shape.RenderTransform = new MatrixTransform (m);
                return;
            }
        }
    }
}

All shape manipulation callbacks from the right toolbar determine the currently selected shape via it's extension property SelectedProperty accessors GetSelected() and manipulate it's RenderTransform accordingly.

Main view model file context

The MainWindowViewModel.cs file remains in initial state. There is no functionality provided to the application by the ViewModel.

Main model file context

The MainModel.cs file remains in initial state. There is no functionality provided to the application by the Model.

Points of Interest

This is another XAML application for X11, almost fully compatible with Microsoft®, showing the main advantages of this approach: The 100% compatible GUI definition and the savings of code lines to create the GUI.

And i've got to now another advantage: The WPF dependency mechanism enables extensions to existing classes, that can be achieved easyly without inheritance.

History

The first version of this article is from 16. June 2015.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)