Download XamlCalcApp_X11 V1_32.zip / Download XamlCalcApp_X11_V2_32.zip
Download XamlCalcApp_X11_V1_64.zip / Download XamlCalcApp_X11_V2_64.zip
Download XamlCalcApp_Win7_V1.zip / Download XamlCalcApp_Win7_V2.zip
Introduction
This article is a case study, how to write a MVVM (Model View ViewModel) design pattern based X11 application (utilizing basic menu, clipboard, default validation, key binding and custom validator features) 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 and Writing a XAML application for X11 with massive data binding and zero code. 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 more complex 'proof of concept' and checks out if and how it is possible bo create MVVM design pattern based X11 application with XAML.
Since this fourth 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
and 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,
this article shall demonstrate 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.
The updated version of this article shall demonstrate as well
- custom binding validation and
- keyboard shortcut binding.
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):
- XamlCalcApp 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.8, 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.8, 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: The number 12 is memorized (M) and should be multiplied with the next value (12 *), but the entered value (#4) is faulty - it can't be converted to a number. This is indicated by the red border around the TextBox.
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 but full functional calculator. It contains (top down) a simple menu bar with the one-level menus File and ?, a TextBox that accepts keyboard input, two TextBlocks that show the memory state (M means a number is memorized) and the previously captured operator (e. g. 12 *) as well as multiple rows and columns of buttons including a row spannning button and a column spanning button.
All 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 TextBox automatically examines the input for non-numerical characters and, if input can't be converted to a number, it displays a red border around the TextBox (as shown by the images). There is no auto-correction for typing errors to keep the sample application simple and demonstrate the data validation, utilizing the built-in converter exception, on X11 and Windows.
The sample application is based on the ideas behind the CodeProject article Calc# - An introduction to Gtk# by Priyank Bolia. But instead of a Gtk# indroduction, this article is intended to introduce XAML.
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 button definition.
<Window x:Class="XamlCalcApp.MainView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:src="clr-namespace:XamlCalcApp"
xmlns:validators="clr-namespace:XamlCalcApp"
Name="MainWindow" Title="XAML calculator application"
Width="300" Height="320" Icon="XrwIcon16.bmp">
<Window.Resources>
<src:MainWindowViewModel x:Key="MainViewModel" />
</Window.Resources>
<Grid Name="MainGrid" Background="#E8E8E8" DataContext="{StaticResource MainViewModel}">
<Grid.Resources>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.1*"/>
<ColumnDefinition Width="1.0*"/>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="1.0*"/>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="1.0*"/>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="1.0*"/>
<ColumnDefinition Width="0.2*"/>
<ColumnDefinition Width="1.0*"/>
<ColumnDefinition Width="0.1*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="22"/>
<RowDefinition Height="32"/>
<RowDefinition Height="18"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="0.8*"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="1.0*"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="1.0*"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="1.0*"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="1.0*"/>
<RowDefinition Height="0.2*"/>
<RowDefinition Height="1.0*"/>
<RowDefinition Height="0.1*"/>
</Grid.RowDefinitions>
<Menu Name="MainMenu" Grid.Row="0" Grid.Column="1" Grid.ColumnSpan="9" >
<MenuItem Name="MenuItemFile" Header="File">
<MenuItem Name="MenuItemFileCopy" Header="Copy"
Click="MenuItemFileCopy_Click" />
<MenuItem Name="MenuItemFilePaste" Header="Paste"
Click="MenuItemFilePaste_Click" />
<Separator/>
<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>
<TextBox Name="Entry" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="9"
TextAlignment="Right" FontSize="18" BorderThickness="2" BorderBrush="#CCCCCC">
<TextBox.Text >
<Binding Path="CurrentValue" Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
<TextBlock Name="Memory" Grid.Row="2" Grid.Column="1" Text="" />
<TextBlock Name="Info" Grid.Row="2" Grid.Column="3" Text=""
Grid.ColumnSpan="7" TextAlignment="Right" />
...
</Grid>
</Window>
The complete XAML code is fully Microsoft® compatible.
Compared to Writing a XAML application for X11 with massive data binding and zero code and previous articles, only the enhancments and differences will be discussed.
The Application
will be defined (in addition to the already known features) by:
- The
xmlns:validators
attribute defines the namespace name used for custom validator classes (typically derived from ValidationRule
abstract class). This attribute has been added to do some validation rule tests in conjunction with the custom TextIsFloatingNumberValidationRule
class. Even if the TextIsFloatingNumberValidationRule
class is provided by the sample project, this attribute is currently not fully implemented for X11.
The Menu
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 Menu
control contains MenuItem
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
Header
attribute defines the menu item display text. This attribute is recommended. Currently hotkey markup (e. g. "_File" for [Alt]+[f] hotkey) is not supported. - 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 TextBox
will be defined (in addition to the already known features) by:
- The
TextAlignment
attribute defines the horizontal alignment of the text to display. This attribute is optional. - The
FontSize
attribute defines the font size of the text to display. This attribute is optional. - The
BorderBrush
attribute defines the color of the control's border. This attribute is optional. The border is visible only if the BorderThickness
is set to a value > 0. Currently named colors and HTML color names ("#RRGGBB") are supported. - The
Text
attribute defines the text to display. This attribute is is recommended. The syntax of the attribute can be either "
<value>"
for constant values or {Binding
<path>, ElementName=
<control name>, Mode=
<mode>, UpdateSourceTrigger=
<Trigger>}
for dynamic resources (shorthand notation). A dynamic data source must implement INotifyPropertyChanged
. Now the Mode
is implemented for OneWay
, TwoWay
and OneWayToSource
binding modes. The UpdateSourceTrigger
currently supports Default
, LostFocus
and PropertyChanged
. Beside the shorthand notation for dynamic resources, now the explicit notation is supported as well: <TextBox ...><TextBox.Text ><Binding
<BindingExpression> /></TextBox.Text></TextBox>
. where <BindingExpression> can consist of Path="
<path>" Mode="
<mode>" UpdateSourceTrigger="
<trigger>"
.
There are three alternative TextBox
definitions contained in MainView.xaml. The first one, commented out, contains a fully functional shorthand notation (but shorthand notation doesn't support custom validation). The second one, active, contains a fully functional explicit notation. The support of the explicit notation is new to the Xrw assembly version 0.8. The third one, commented out, contains an experimental explicit notation including a custom validator. This feature isn't fully implemented with the first version of XamlCalcApp. (It is proved - by the Windows version of the sample application - to be not helpful for the specific implementation of the calculator sample application's first version).
The TextBlock
(it is similar to Label
but supports automatic line break) will be defined (in addition to the already known features) by:
- The
TextAlignment
attribute defines the horizontal alignment of the text to display. This attribute is optional.
Back to the Button
s now. This is the XAML file extract with the button definitions, ommitted above.
...
<Button Name="MemoryClear" Grid.Row="4" Grid.Column="1"
Click="MemoryClear_Click" Content="MC" />
<Button Name="MemoryRecall" Grid.Row="4" Grid.Column="3"
Click="MemoryRecall_Click" Content="MR" />
<Button Name="MemorySave" Grid.Row="4" Grid.Column="5"
Click="MemorySave_Click" Content="MS" />
<Button Name="MemoryAdd" Grid.Row="4" Grid.Column="7"
Click="MemoryAdd_Click" Content="M+" />
<Button Name="MemorySubstract" Grid.Row="4" Grid.Column="9"
Click="MemorySubstract_Click" Content="M-" />
<Button Name="EditDeleteLast" Grid.Row="6" Grid.Column="1"
Click="EditDeleteLast_Click" Content="←" />
<Button Name="EditClearEntry" Grid.Row="6" Grid.Column="3"
Click="EditClearEntry_Click" Content="CE" />
<Button Name="EditClearAll" Grid.Row="6" Grid.Column="5"
Click="EditClearAll_Click" Content="C" />
<Button Name="CalculateDivide" Grid.Row="6" Grid.Column="7"
Click="CalculateDivide_Click" Content="/" />
<Button Name="CalculateSqrt" Grid.Row="6" Grid.Column="9"
Click="CalculateSqrt_Click" Content="x ¹/²" />
<Button Name="NumberSeven" Grid.Row="8" Grid.Column="1"
Click="NumberSeven_Click" ><TextBlock Text="7" FontWeight="Bold" /> </Button>
<Button Name="NumberEight" Grid.Row="8" Grid.Column="3"
Click="NumberEight_Click" ><TextBlock Text="8" FontWeight="Bold" /> </Button>
<Button Name="NumberNine" Grid.Row="8" Grid.Column="5"
Click="NumberNine_Click" ><TextBlock Text="9" FontWeight="Bold" /> </Button>
<Button Name="CalculateMultiply" Grid.Row="8" Grid.Column="7"
Click="CalculateMultiply_Click" Content="*" />
<Button Name="CalculateSquare" Grid.Row="8" Grid.Column="9"
Click="CalculateSquare_Click" Content="x ²" />
<Button Name="NumberFour" Grid.Row="10" Grid.Column="1"
Click="NumberFour_Click" ><TextBlock Text="4" FontWeight="Bold" /> </Button>
<Button Name="NumberFife" Grid.Row="10" Grid.Column="3"
Click="NumberFife_Click" ><TextBlock Text="5" FontWeight="Bold" /> </Button>
<Button Name="NumberSix" Grid.Row="10" Grid.Column="5"
Click="NumberSix_Click" ><TextBlock Text="6" FontWeight="Bold" /> </Button>
<Button Name="CalculateSubtract" Grid.Row="10" Grid.Column="7"
Click="CalculateSubtract_Click" Content="-" />
<Button Name="CalculateReciprocal" Grid.Row="10" Grid.Column="9"
Click="CalculateReciprocal_Click" Content="1/x" />
<Button Name="NumberOne" Grid.Row="12" Grid.Column="1"
Click="NumberOne_Click" ><TextBlock Text="1" FontWeight="Bold" /> </Button>
<Button Name="NumberTwo" Grid.Row="12" Grid.Column="3"
Click="NumberTwo_Click" ><TextBlock Text="2" FontWeight="Bold" /> </Button>
<Button Name="NumberThree" Grid.Row="12" Grid.Column="5"
Click="NumberThree_Click" ><TextBlock Text="3" FontWeight="Bold" /> </Button>
<Button Name="CalculateAdd" Grid.Row="12" Grid.Column="7"
Click="CalculateAdd_Click" Content="+" />
<Button Name="CalculateResult" Grid.Row="12" Grid.Column="9"
Click="CalculateResult_Click" ontent="=" Grid.RowSpan="3" />
<Button Name="NumberZero" Grid.Row="14" Grid.Column="1" Grid.ColumnSpan="3"
Click="NumberZero_Click" ><TextBlock Text="0" FontWeight="Bold" /> </Button>
<Button Name="NumberDecimalDot" Grid.Row="14" Grid.Column="5"
Click="NumberDecimalDot_Click" ><TextBlock Text="." FontWeight="Bold" /> </Button>
<Button Name="CalculateNegate" Grid.Row="14" Grid.Column="7"
Click="CalculateNegate_Click" Content="+/-" />
...
The Button
control syntax already included the possibility to have a TextBox
child with the very first Xrw XAML version. But internally the TextBox
child hasn't been represented by a TextBox
control, because the Button
control has been implemented based on XrwButton
. Instead the TextBox
child's Text
property content has been assigned to the Button
control's Content
property content. Now a Button
control always contains a TextBox
child control, because the Button
control is implemented based on XrwFrame
. If there is an explicit TextBox
child control defined in the XAML file, the default TextBox
child control will be replaced by the explicit definition.
Limitations of the XAML syntax
X11/Xrw implementation:
- The
MenuItem
control currently doesn't support hotkey markup (e. g. "_File" for [Alt]+[f] hotkey). - The
MenuItem
control nesting depth is currently limited to 2. - The
Binding
syntax currently doesn't support binding mode Mode=OneTime
and trigger UpdateSourceTrigger=Explicit
. - The
Binding
syntax doesn't support custom validators (with the first version of this article - the second version supports this feature).
Second version of XAML (MainView.xaml)
The XAML file is completed with key bindings.
...
</Window.Resources>
<Window.InputBindings>
<KeyBinding Command="{Binding NumberZero}" Key="NumPad0"/>
<KeyBinding Command="{Binding NumberZero}" Key="D0"/>
<KeyBinding Command="{Binding NumberOne}" Key="NumPad1"/>
<KeyBinding Command="{Binding NumberOne}" Key="D1"/>
<KeyBinding Command="{Binding NumberTwo}" Key="NumPad2"/>
<KeyBinding Command="{Binding NumberTwo}" Key="D2"/>
<KeyBinding Command="{Binding NumberThree}" Key="NumPad3"/>
<KeyBinding Command="{Binding NumberThree}" Key="D3"/>
<KeyBinding Command="{Binding NumberFour}" Key="NumPad4"/>
<KeyBinding Command="{Binding NumberFour}" Key="D4"/>
<KeyBinding Command="{Binding NumberFife}" Key="NumPad5"/>
<KeyBinding Command="{Binding NumberFife}" Key="D5"/>
<KeyBinding Command="{Binding NumberSix}" Key="NumPad6"/>
<KeyBinding Command="{Binding NumberSix}" Key="D6"/>
<KeyBinding Command="{Binding NumberSeven}" Key="NumPad7"/>
<KeyBinding Command="{Binding NumberSeven}" Key="D7"/>
<KeyBinding Command="{Binding NumberEight}" Key="NumPad8"/>
<KeyBinding Command="{Binding NumberEight}" Key="D8"/>
<KeyBinding Command="{Binding NumberNine}" Key="NumPad9"/>
<KeyBinding Command="{Binding NumberNine}" Key="D9"/>
<KeyBinding Command="{Binding NumberDecimalDot}" Key="Decimal"/>
<KeyBinding Command="{Binding CalculateDivide}" Key="Divide"/>
<KeyBinding Command="{Binding CalculateDivide}" Key="D7" Modifiers="Shift"/>
<KeyBinding Command="{Binding CalculateMultiply}" Key="Multiply"/>
<KeyBinding Command="{Binding CalculateMultiply}" Key="OemPlus" Modifiers="Shift"/>
<KeyBinding Command="{Binding CalculateSubtract}" Key="Subtract"/>
<KeyBinding Command="{Binding CalculateSubtract}" Key="OemMinus"/>
<KeyBinding Command="{Binding CalculateAdd}" Key="Add"/>
<KeyBinding Command="{Binding CalculateAdd}" Key="OemPlus"/>
<KeyBinding Command="{Binding CalculateResult}" Key="D0" Modifiers="Shift"/>
</Window.InputBindings>
<Grid ...
The Window.InputBindings
node contains KeyBinding
nodes and they will be defined by:
- The
Command
attribute defines the "RelayCommand" to execute. This attribute is is mandatory. The syntax of the attribute is {Binding
<path>}
. - The
Key=
<key> defines any value of the System.Windows.Input.Key
enumeration. This statement is mandatory. - The
[Modifiers=
<modifiers>]
defines any value of the System.Windows.Input.ModifierKeys
enumeration. This statement is optional. The default/fallback value is System.Windows.Input.ModifierKey.None
.
Limitations of the XAML syntax
X11/Xrw implementation:
- The
KeyBinding
's Command
currently supports ICommand
properties of the current DataContext
only. - The
KeyBinding
currently doesn't support CommandParameter
and CommandTarget
. - The
KeyBinding
's Key
requires localization of key codes.
Some desirable key bindings can't be defined, e.g. there is no System.Windows.Input.Key
value for '.' or ','. Some key binding won't work on other keyboards than GERMAN. This is because of
- the keyboard layout and
- the
System.Windows.Input.KeyConverter
's KeyToKeySym()
implementation.
It might be necessary to change / extend both, to get ENGLISH keyboards working perfectly.
The code behind (MainView.xaml.cs)
The corresponding C# code file of the main view is MainView.xaml.cs
. It contains all the necessary structures and attributes to implement the value calculaton as well as all click handler code.
public partial class MainView : XrwXAML.Window, IView
{
private enum SecondArgumentAction
{
None,
Add,
Substract,
Multiply,
Divide,
Clear
}
private static MainView _instance = null;
private SecondArgumentAction _operator = SecondArgumentAction.None;
private double _value = 0;
private double _memValue = 0;
private string _decimalDelimiter =
CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
private NumberFormatInfo _invariantNF = CultureInfo.InvariantCulture.NumberFormat;
public MainView ()
: base (-1, -1)
{
_instance = this;
InitializeComponent ();
}
...
private void MenuItemFileCopy_Click(object sender, RoutedEventArgs e)
{
Clipboard.SetText (Entry.Text);
}
private void MenuItemFilePaste_Click(object sender, RoutedEventArgs e)
{
Clipboard.GetText (this.ProcessClipboardPasteToEntry);
}
...
}
The code block doesn't contain all the click handlers - they would mainly be repetitions. There is one thing, that should be poited out nevertheless: Clipboard text transfer.
The System.Windows
static class Clipboard
contains the methods SetText()
and GetText()
and they have equivalents within Xrw version 0.8 now. But it has been inpossible to implement the GetText()
method fully Windows compatible (that means parameterless). Because of considerable differences between the monolithic design of Window's Desktop-Windows-Manager compared to the X11 Windows-Manager, the GetText()
method expects a delegate to process the asynchronously provided clipboard result. The sample application implements ProcessClipboardPasteToEntry()
to realize this (see source code above for the sample application's X11 version implementation).
The sample application's Windows version implements MenuItemFilePaste_Click()
this way:
private void MenuItemFilePaste_Click(object sender, RoutedEventArgs e)
{
string rawPastable = Clipboard.GetText();
ProcessClipboardPasteToEntry(rawPastable);
}
The ProcessClipboardPasteToEntry()
delegate of the sample application's X11 version and the ProcessClipboardPasteToEntry()
method of the Windows version share exactly the same code:
private void ProcessClipboardPasteToEntry (object result)
{
if (result != null)
{
string rawPastable = result.ToString ();
string cleanPastable = "";
for (int charIndex = 0; charIndex < rawPastable.Length; charIndex++)
{
if (char.IsDigit (rawPastable[charIndex]))
cleanPastable += rawPastable[charIndex];
else
{ string s = rawPastable.Substring (charIndex, 1);
if (_decimalDelimiter == s || "." == s)
cleanPastable += _decimalDelimiter;
else
break;
}
}
if (!string.IsNullOrEmpty (cleanPastable))
{
double d = 0;
if (double.TryParse (cleanPastable, out d) == true)
{
Entry.Text = d.ToString ();
}
}
}
}
Main view model file context
The only thing, the MainWindowViewModel.cs
file contains beyond the initial state, is the CurrentValue
property, the TextBox
Entry is bound to.
...
#region Attributes
...
private string _currentValue = 0;
#endregion
...
#region Properties
...
public string CurrentValue
{ get
{ return _currentValue; }
set
{ if (_currentValue == value)
return;
_currentValue = value;
RaisePropertyChanged("CurrentValue");
}
}
#endregion
....
The binding of the CurrentValue
property to the TextBox
Entry's Text
property is done in three steps:
- The static resource
MainViewModel
is bound to a MainWindowViewModel
instance by the XML node <Window.Resources>
. - The Grid connects the static resource
MainViewModel
to it's DataContext
property. - The
TextBox
Entry binds the DataContext
's CurrentValue
property to it's Text
property.
...
<Window.Resources>
<src:MainWindowViewModel x:Key="MainViewModel" />
</Window.Resources>
...
<Grid Name="MainGrid" Background="#E8E8E8" DataContext="{StaticResource MainViewModel}">
...
<TextBox Name="Entry" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="9"
TextAlignment="Right" FontSize="18" BorderThickness="2" BorderBrush="#CCCCCC">
<TextBox.Text >
<Binding Path="CurrentValue" Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged" />
</TextBox.Text>
</TextBox>
...
</Grid>
First version of XamlCalcApp
There is no special functionality associated with the bound MainWindowViewModel
's CurrentValue
property. But since it is of type System.Double
and the TextBox
Entry's Text
property is of type System.String
, the binding requires automatic type conversion. A failed type conversion throws an exception. For the TextBox
Entry's Text
"#4", as shown in the sample application images, the exception is:
System.Windows.Data Error: 7 : ConvertBack cannot convert value '#4' (type 'String'). BindingExpression:Path=CurrentValue; DataItem='MainWindowViewModel' (HashCode=44944057); target element is 'TextBox' (Name='Entry'); target property is 'Text' (type 'String') ...
This ecxeption is catched by the data binding and leads to a red border around the TextBox, indicating the typing error. Currently there is no auto-correction for erroneous entry to keep the sample application simple and demonstrate the data validation.
Next functionality
The next things, that could be implemented, are
- a built-in auto-correction integrated into
MainWindowViewModel
's CurrentValue
property setter and - an advanced keyboard support, that simulates the button press events for [0] ... [9], [.], [+], [-], [*] [/] and [=].
Second version of XamlCalcApp
The definition of the CurrentValue
property as a System.Double
data type has one big drawback: Floating point numbers can't be entered. The second version of this sample application changes the CurrentValue
property data type to System.String
. But now the automatic type conversion is unnecessary (both, the TextBox
Entry and MainWindowViewModel
properties are of type System.String
) and typing errors don't throw a System.Windows.Data Error exception any longer (based on this, the TextBox
Entry indicates the error by a red border around the TextBox
).
Now there is a validation rule bound to the TextBox
Entry and a custom validator is implemented instead:
<TextBox Name="Entry" Grid.Row="1" Grid.Column="1" Grid.ColumnSpan="9"
TextAlignment="Right" FontSize="18" BorderThickness="2" BorderBrush="#CCCCCC">
<TextBox.Text >
<Binding Path="CurrentValue" Mode="TwoWay" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<validators:TextIsFloatingNumberValidationRule
ErrorMessage="The input must be a valid floating point number." />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
This is the validator class. The error message text is injected via the ErrorMessage
property from the XAML code.
public class TextIsFloatingNumberValidationRule : ValidationRule
{
private string _errorMessage;
public TextIsFloatingNumberValidationRule ()
{
}
public string ErrorMessage
{
get { return _errorMessage; }
set { _errorMessage = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (cultureInfo == null)
cultureInfo = CultureInfo.CurrentCulture;
string localDecimalSeparator =
cultureInfo.NumberFormat.NumberDecimalSeparator;
string invarDecimalSeparator =
CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator;
string inputString = (value ?? string.Empty).ToString();
for (int charIndex = 0; charIndex < inputString.Length; charIndex++)
{
char c = inputString[charIndex];
if (!char.IsNumber(c) &&
localDecimalSeparator[0] != c &&
invarDecimalSeparator[0] != c)
return new ValidationResult(false, this.ErrorMessage);
}
return new ValidationResult(true, null);
}
}
This provides the same typing error notification funtionality for the second version of the sample application as well.
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
It was an interesting work to create this XAML application for X11, almost fully compatible with Microsoft®. The longer i have been involved with parallel development in X11 and Windows based on XAML (this is the fourth case study), the more i'm convinced of XAML. This applies in particular to the 100% compatible GUI definition and the savings of code lines to create the GUI.
Negative aspects, as the many static objects and properties XAML requires (e. g. tons of System.Windows.DependencyProperty
instances), are increasingly fading into the background.
History
The first version of this article is from 2. February 2015.
This is the second version, frviewed and extended, from 16. February 2015.