Download XamlGeometryApp_X11_32.zip Mono project including full source and executable
Download XamlGeometryApp_X11_64.zip Mono project including full source and executable
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.
The second image shows the sample application on OPEN SUSE 12.3 Linux 64 bit DE and Xfce.
The third image shows the sample application on Windows® 7 64 Bit Edition.
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.
<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">
<Window.Resources>
<ResourceDictionary>
<src:MainWindowViewModel x:Key="MainViewModel" />
</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>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70" />
</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>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="80" />
</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.
<Rectangle Name="ImageRect" Width="278" Height="227" Canvas.Left="57" Canvas.Top="10">
<Rectangle.Fill>
<ImageBrush ImageSource="Bridge.bmp"/>
</Rectangle.Fill>
</Rectangle>
<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" />
<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
, StrokeDashArray
, RenderTransformOrigin
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
, StrokeDashArray
, RenderTransformOrigin
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
, StrokeDashArray
, RenderTransformOrigin
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).
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.
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)
{
}
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.
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)
{
}
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.