CCCR: Context Commands, Converters and Rules





5.00/5 (1 vote)
This article offers unified approach to implementation of user commands, converters, and validation rules. Those who are very curious can jump directly to the chapter Demonstration and to the source code (demo version with the original library is attached).
Contents
- Designations
- Introduction
- Principles of CCCR
- Implementation
- Comparative analysis
- Prospects
- Demonstration
- Notes
- Conclusion
Designations
CCCR | Context Commands, Converters and validation Rules |
CCR | Commands, Converters and validation Rules (in wider sense than CCCR) |
Alpha | Category of functional entities of CCCR, initialized in the main application code |
Beta | Category of functional entities of CCCR, initialized in XAML code |
The article applied naming the entities rule, according to which more general concept
goes before the notions of less general but more specific. For example, there is an
interface IInvoker
, which is partially implemented by an abstract class
InvokerBase
; further, from which a class InvokerAlpha
is generated;
and at the same level of hierarchy there is a generated group of classes InvokerBetaCS
,
InvokerBetaJS
,InvokerBetaFS
,InvokerBetaVB
,
InvokerBetaCSLambda
. This approach makes it possible to display lists of entities
in natural manner grouped according to the depth of common concepts – according to meaning.
Introduction
Those readers of this article, who dealt with the construction of WPF applications, faced with the need to implement three significant components: commands, converters, and validation rules. Often (of course, not always) a task the solution of which needs a component is so specific, that the reuse of implementation either impossible or requires complex unnatural generalizations. Moreover there is often a need to create only one level of logic in any of N-level application model. In the latter case transference of this logic for the implementation on the other level of the application model blurs the source code with all resulting consequences. The essence of the idea is not to reinvent a new wheel every time for new roads, once again giving rise to narrowly focused classes, but, using the same wheeled terminology, to be able to replace the wheels depending on the terrain and requirements. It is a philosophy of declarative programming when polymorphism gives way to functional programming tools.
Principles of CCCR
As it was previously mentioned, the ultimate goal of contextual approach is to offer developers a consistent mechanism to work with the entities of the triad CCR, - mechanism which is applicable both in the XAML-markup of user Interface and in the source code itself. Such is the strategy. Tactically, the following tasks are solved: simplicity of use, extensibility, declarative nature, portability. This refers to affluence of functional with resource constraints (“beauty without diversity of colors”, - by Andrei Rublev). This also refers to the possibility of further detailed elaboration without need to change the base code. This refers too, at least in general terms, to preservation of the declarative approach as well. Finally, it was assumed to be independent of programming language and to be able to transfer code (almost without changes) of CCCR’s application from XAML to the main code and vice versa.
Implementation
The subject of this section is about the implementation of CCCR in the context of using it by the end-user.
Contexts
Different types of tasks require different input and output data and different scripts
of behavior. If for commands the implementation of ICommand
interface with its two
methods CanExecute
, Execute
and CanExecuteChanged
event
is required, then for converters the implementation of IValueConverter
,
IMultiValueConverter
interfaces with their Convert
and
ConvertBack
methods are required. In its turn validation rules are obliged
to implement Validate
method of ValidationRule
abstract class.
It is not worth talking that each method has its own set of parameters. Single thing that
unites them is that any of the components has some execution context with its number of
input and output data and additional parameterization. The basis for the implementation
has been laid! As in arithmetic, when the total is factorized out, so it is in our case
total data and parameterization are imposed out into IContex
interface,
binding its descendants to implement their members: properties of arrays objects
In
, Out
, and objects of properties Parameter
and Source
. Specificity of a separate entity remained in brackets (see above).
The figure below shows the diagram of ContextCommand
, ContextConverter
,
ContextRule
classes.
![]() ![]() |
Having specified the contexts in this way we opened the opportunity to turn invocations of virtual
methods of CCCR base interfaces/classes (ICommand
for Commands, IValueConverter
,
IMultiValueConverter
for Converters and ValidationRule
for validation Rules) into an
invocation of any one class method which accepted the obligation to perform certain
types of functions. An Invoker (IInvoker
), so we call the basic interface of these
classes, describes two key members in understanding the CCCR concept: property Context
of generalized type TContex
(generated from IContext
), and method Invoke
.
The names of the members are quite self-sufficient to speak for themselves: Context
- the current
context to call an abstract method Invoke
. Since the method invocation may be accompanied
by emission of exceptions IInvoker
, interface is generated from IExceptor interface with
LastException
properties (the last exception occurred while invoking the method) and
NoException
of Boolean
type, denoting the need or no need to overdrive exceptions when
they are being intercepted. Besides the interface of Invoker itself (IInvoker
), similar
exception handling mechanism should be implemented by each entity of CCCR. Next the
article will discuss two different categories Alpha
and Beta
of these entities. Here we
limit ourselves to the fact, that the categories are associated with ICCCRAlpha
and
ICCCRBeta
interfaces. The figure below shows the dependency graph of these interfaces.
![]() ![]() |
Categories
The desire to use the CCCR in two different environments – in XAML-markup and in main code
– is dictated by the need to divide the functional into two categories where one is responsible
for creating the entity of CCCR in the main code, and the other is in XAML. Both Greek origin
of the author of the article and some determinacy of the implementation from Lambda-expressions
directed the author to the idea to use the Greek alphabet letters to refer to these categories.
However, all artists tend to grant their features to the person portrayed. So, Alfa
category
is a category of the first kind (main code), Beta
category is a functionality category of the
second kind (XAML). Entities of Alpha
category accept the input of Action<TContext>
delegates
(ICCCRAlpha.Function
property). Entities of Beta category accept the input of the code string
(ICCCRBeta.Code
property). In the current implementation for writing code languages C#, VB.Net,
JScript, F# can be used plus some limited syntax of Lambda-expressions of language C#. Obviously,
the category of the first kind will prevail in the usage by the end-user. This logic is also
accentuated in the titles of categories: Alpha
letter advances before letter Beta
.
The figure below shows the dependency diagram of entities of both categories.
![]() ![]() ![]() |
Invokers
The hierarchy is simple. A significant part of the classes refers to Beta category
under the implementation of language differences. One could do without the classes
of end (concrete) entities of CCCR, as far as they only instantiate generic classes
of CCCR, but, firstly XAML does not "understand" generics, and, secondly the presence
of abridged notation of the type significantly simplifies the code. The most significant
part of the code is concentrated in the base classes of CCCR and classes of Invoker
s.
The following diagram shows weak link of the CCCR entities with Invoker
classes
(relations are shown by the arrows, and, as it can be seen, all relations are mediated
through the interfaces).
![]() ![]() |
Invoker
s will be examined in more precise detail. Their role lies in the fact
that in some way they interpret the information about a certain method from
outside. By the way, it is not necessarily from the CCCR entities, but, in
principle, just from the code. A user (in broad sense) a) creates an Invoker
,
specifying the type of context, b) initializes the context and c) calls the
Invoke
method. Technically, the Invoke
method is a
public template method of a famous design pattern behind which there is an
abstract protected _Invoke
method. Such is the brief implementation
of an abstract InvokerBase
class. The Alpha
and Beta
categories give specifics. In this way the Invoke method of the InvokerAlpha
class only invokes a delegate passed in it. The same method of an InvokerBetaBase
class has already been sealed interrupting on itself polymorphic difference between
the behaviors of classes of descendants by this method. But on this the polymorphism
of InvokerBetaBase
does not end but acquires a new development in
already new abstract CreateFunction
method which returns
Action<TContext>
delegate. Such mechanism allows us to
calculate the delegate only once and from this moment to invoke only the already
received (cached) delegate
of the call. A replacement of the delegate
takes place
when the associated with it code is changing in string
property IInvoker.Code
.
The following three figures show the hierarchy of
IInvoker
successors and
the described above mechanism for the operation of their Invoke
methods.
![]() ![]() ![]() ![]() |
![]() ![]() |
The implementation of the ICCCRAlpha
and ICCCRBeta
interfaces is shown on the following sequence diagrams of invocations.
![]() ![]() |
CCCR
Earlier in the article there were described mechanisms for implementation of CCCR in the bondContext
-Invoker
. Next, attention
will be paid to a simpler
bond Invoker
-CCCR
and on the whole the chain of reasoning
about the CCCR implementation will be enclosed. Fortunately, a terse statement can be put here
– the section "Context" has already touched on the specifics of each of the CCCR
entities. Thus, any CCCR entity contains Invoker
property. The base command class
CommandBase
packs the input data obtained by invocation of overridden
CanExecute
and Execute
methods in an array of objects
IContext.In
of the context of its Invoker
and calls Invoke
method. Similar things occur with the two remaining CCCR entities Convertor and validation Rules.
ConverterBase and RuleBase
classes will be mentioned here. For the first one
call context is conditioned by Convert
and ConvertBack
methods, for
the second one – Validate
. Again, the input data are packed into
an array of objects, IInvoker.Invoke
method is called, and then Invoker.Out
array of objects is passed to output from the Convert
,
ConvertBack
and Validate
methods.
The hierarchy of command classes, converter classes, and validation rule classes is shown in the figures below.
![]() ![]() |
![]() ![]() |
![]() ![]() |
Comparative Analysis
The following table shows the qualitative characteristics of the existing
implementations of Invoker
s – the CCCR classes themselves are only their
"clients" and have no effect on the performance and logics.
Invokers | Description |
---|---|
Alpha |
Perfomance: maximum (5) Caching: not required Requirements to sys. resources: minimal (1) Usage: only main code
|
Beta JScript |
Perfomance: satisfactory (3) Caching: impossible in current implementation (possible in case of rejection of Eval method and the transition to the use
JScriptCodeDomProvider , then the figures would be similar to C# and VB.Net)Requirements to sys. resources: low (2) Usage: code, XAML
|
Beta C# |
Perfomance: good (4), when the cached function is called Caching: implemented, the waiting time is satisfactory (3) Requirements to sys. resources: high (4), one delegate – one assembly Usage: code, XAML
|
Beta C# Lambda |
Perfomance: good (4), when the cached function is called Caching: implemented, the waiting time is good (4) Requirements to sys. resources: low (2) Usage: code, XAML Limitations: the operation is only possible with the predefined types (elementary, Math , Convert )
|
Beta VB.Net | Similarly to Beta C#
|
Beta F# |
Perfomance: satisfactory (3), calling the cached function Caching: implemented, the waiting time is good (2) Requirements to sys. resources: high (4), one delegate – one assembly Usage: code, XAML
|
Prospects
From the viewpoint of increasing productivity of Invokers of Beta C#, Beta VB.Net, Beta F#, it is possible to implement a preliminary loading of XAML code in order to cache generated delegates and generate a single assembly for the entire set of delegates (in this implementation creation of a separate delegate leads to the creation of separate assembly in the computer memory). In this case, the generation of such assemblies will be required only once during all time of application (work) and on the side of the developer, so the performance problems can be forgotten forever!
Demonstration
Perhaps the most interesting point in the article – these are examples of code, actually, for the sake of what this quite bulk work was started.
Below there is a fragment of XAML-code. It specifies the CommandBetaFS
– the command in F# language that initiates a process given whose name
is defined by CommandParameter
. It should be reminded that in the context
of the CommandParameter
the ContextCommand
gets
a new name Context.Parameter
. In view of the fact that
Context.Parameter
has object type, do not forget
to cast it to the correct type (in this case, ToString
). F# language is
sensitive to indentations, so the script of the command is placed in
the string tag with a definition value of “preserve”
the field xml:space
.
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaFS : Run Notepad"
CommandParameter="notepad.exe"
>
<Button.Command>
<CCCR:CommandBetaFS
AlwaysCanExecute="True">
<CCCR:CommandBetaFS.Code>
<system:String xml:space="preserve">
let s = Context.Parameter.ToString()
System.Diagnostics.Process.Start(s)
</system:String>
</CCCR:CommandBetaFS.Code>
</CCCR:CommandBetaFS>
</Button.Command>
</Button>
Below there is a fragment of the main application code written in the C# language.
Here we use the class constructor CommandAlpha
with the delegate
Action<ContextCommand>
as a parameter. If the command parameter is empty,
a fixed string is given instead and then is passed to the MessageBox.Show
method.
It should be noted that the same code that is passed as a delegate to the CCCR
constructor of the Alpha
category, can be used with absolutely no changes
for a pass in the shape of a string to the script in the CCCR constructor
of Beta
category.
new CommandAlpha (
Context => {
if (Context.GetCanExecute)
Context.CanExecute = true;
else
MessageBox.Show(
(Context.Parameter ?? "Alpha1-(NO PARAMs)").ToString()); } );
The following piece of XAML markup specifies the ConverterBetaCSLambda
converter. It the case of direct conversion a prefix “Forward: ” is added to the input,
in the case of reverse transformation another prefix “Back: ” is used.
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Target:</TextBlock>
<TextBox x:Name="m_tTxt1"
Text="{Binding
ElementName=m_tTxt2,
Path=Text,
UpdateSourceTrigger=PropertyChanged,
Converter={CCCR:ConverterBetaCSLambda Code=
'(Context.IsBack ? "Back: " : "Forward: ") + (Context.In[0]).ToString()',
NoExceptions=False}}"
/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source:</TextBlock>
<TextBox x:Name="m_tTxt2"></TextBox>
</DockPanel>
The following is a snapshot of the demo application and here provided a more complete
snippet of XAML-markup.
C#
private static CommandAlpha m_tCommandAlpha1;
public static CommandAlpha CommandAlpha1 {
get {
if (m_tCommandAlpha1 == null)
m_tCommandAlpha1 =
// Creating a command CommandAlpha, which to the request CanExecute always returns True,
// and displays a dialog box with the message given by parameter CommandParameter.
new CommandAlpha (
Context => {
if (Context.GetCanExecute)
Context.CanExecute = true;
else
MessageBox.Show(
(Context.Parameter ?? "Alpha1-(NO PARAMs)").ToString()); } );
return m_tCommandAlpha1; } }
private static CommandAlpha m_tCommandAlpha2;
public static CommandAlpha CommandAlpha2 {
get {
if (m_tCommandAlpha2 == null)
// Creating a command CommandAlpha as above, but in a slightly different syntax.
m_tCommandAlpha2 =
new CommandAlpha (Context =>
MessageBox.Show(!String.IsNullOrWhiteSpace(
(Context.Parameter ?? "").ToString()) ?
Context.Parameter.ToString() : "Alpha2-(NO PARAMs)"))
{ AlwaysCanExecute = true };
return m_tCommandAlpha2; } }
private static CommandBetaJS m_tCommandBetaJS1;
public static CommandBetaJS CommandBetaJS1 {
get {
if (m_tCommandBetaJS1 == null)
// Creating of a command CommandBetaJS as before,
// but here a script is used instead of lambda expressions.
m_tCommandBetaJS1 =
new CommandBetaJS (
@"MessageBox.Show(!String.IsNullOrWhiteSpace(Context.Parameter) ?
Context.Parameter : ""Beta1-(NO PARAMs)"")") { AlwaysCanExecute = true };
return m_tCommandBetaJS1; } }
XAML
<Window.Resources>
<!--
An entry into the window resource of simple command which displays a dialog box
Request CanExecute is not served when the program starts, as
parameter AlwaysCanExecute is initialized in the "True".
-->
<CCCR:CommandBetaJS
x:Key="x_tCmd_MsgBox"
Code="MessageBox.Show(Context.Parameter)"
AlwaysCanExecute="True">
</CCCR:CommandBetaJS>
<!--
An entry of ConverterBetaJS converter into the window resource
to convert String (in) to Integer and vice versa.
-->
<CCCR:ConverterBetaJS x:Key="x_tCnv_JS_0_100" NoExceptions="True">
<CCCR:ConverterBetaJS.Code>
var c : ContextConverter = Context;
if (!c.IsBack) {
var n = int(c.In[0]);
c.Out[0] = n.ToString(); }
else {
var n = Int32.Parse(c.In[0]);
c.Out[0] = n; }
</CCCR:ConverterBetaJS.Code>
</CCCR:ConverterBetaJS>
<!--
This converter ConverterBetaCS is similar to the previous, but additionally
checks the CommandParameter and number on the membership
to the range of values [0, 100].
-->
<CCCR:ConverterBetaCS x:Key="x_tCnv_CS_0_100" NoExceptions="False">
<CCCR:ConverterBetaCS.Code>
try {
var c = Context;
if (c.IsBack == ((c.Parameter ?? "").ToString() == "Slider")) {
var n = Convert.ToInt32(c.In[0]);
c.Out[0] = n.ToString(); }
else {
var s = (c.In[0] ?? "0").ToString().Trim();
int n = 0;
Int32.TryParse(s, out n);
if (0 > n || n > 100)
throw new Exception("Out of range [0; 100]!");
c.Out[0] = n; } }
catch (Exception e) {
/*MessageBox.Show(e.Message);*/ }
</CCCR:ConverterBetaCS.Code>
</CCCR:ConverterBetaCS>
<!--
Style definition TextBox, to display Tooltips when there are validation errors
-->
<Style x:Key="{x:Type TextBox}" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"></Setter>
</Trigger>
</Style.Triggers>
</Style>
<Style x:Key="{x:Type Slider}" TargetType="{x:Type Slider}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
<Setter Property="Background" Value="Red"></Setter>
</Trigger>
<EventTrigger RoutedEvent="ValueChanged">
</EventTrigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Window.InputBindings>
<!--Binding commands to hotkeys.-->
<KeyBinding Key="F1" Command="{x:Static local:WinMain.CommandAlpha1}"/>
<KeyBinding Key="F2" Modifiers="Alt" Command="{x:Static local:WinMain.CommandAlpha2}"/>
<KeyBinding Key="F3" Command="{x:Static local:WinMain.CommandBetaJS1}"/>
<KeyBinding Key="F4" Modifiers="Control"
Command="{CCCR:CommandBetaVB 'System.Diagnostics.Process.Start((Context.Parameter))',
AlwaysCanExecute=True, NoExceptions=True }"
CommandParameter="calc.exe"/>
<KeyBinding Key="F5"
Command="{StaticResource x_tCmd_MsgBox}"
CommandParameter="You pushed F5 key."/>
</Window.InputBindings>
<Grid Margin="10,10,10,10" >
<StackPanel>
<TextBlock TextWrapping="Wrap">
The functionality except the one
of the first three buttons on the left side is fully implemented in XAML.
</TextBlock>
<TextBlock TextWrapping="Wrap">
That became possible by means of an anproach named
<TextBlock Text="CCCR" FontWeight="Bold" /> (Context Commands, Converters and Rules)
</TextBlock>
<Grid Margin="0,6,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="4"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<StackPanel Grid.Column="0">
<TextBlock Margin="0,0,0,10"
Background="{StaticResource {x:Static SystemColors.ControlLightBrushKey}}">
Hotkeys: F1, Alt+F2, F3, Ctrl+F4, F5
</TextBlock>
<!--Binding a command of Alpha category (initialized in the main code)
to the button's Click event.-->
<Button
x:Name="m_tBtnAlpha1"
HorizontalContentAlignment="Left"
Content="Code / CommandAlpha : Show Message Box"
CommandParameter="'This string is a parameter'"
Command="{x:Static local:WinMain.CommandAlpha1}"
/>
<!--Binding a command of Alpha category (initialized in the main code)
to the button's Click event.-->
<Button
x:Name="m_tBtnAlpha2"
HorizontalContentAlignment="Left"
Content="Code / CommandAlpha : Show -//- (Shorter syntax)"
CommandParameter=" "
Command="{x:Static local:WinMain.CommandAlpha2}"
/>
<!--Binding a command of Beta category (initialized in the main code)
to the button's Click event.-->
<Button
x:Name="m_tBtnBetaJS1"
HorizontalContentAlignment="Left"
Content="Code / CommandBetaJS : Show -//-//-"
CommandParameter=" "
Command="{x:Static local:WinMain.CommandBetaJS1}"
/>
<!--Binding a command of Beta category (initialized in the window resources)
to the button's Click event.-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : Show Message Box"
Command="{StaticResource x_tCmd_MsgBox}"
CommandParameter="Command Parameter ABRACADABRA"
>
</Button>
<!--
By the button's Click event a command in F# language is bound.
Pay attention to the use of the markup <system:String xml:space="preserve">
owing to the need to preserve the indentation from the left edge.
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaFS : Run Notepad"
CommandParameter="notepad.exe"
>
<Button.Command>
<CCCR:CommandBetaFS
AlwaysCanExecute="True">
<CCCR:CommandBetaFS.Code>
<system:String xml:space="preserve">
let s = Context.Parameter.ToString()
System.Diagnostics.Process.Start(s)
</system:String>
</CCCR:CommandBetaFS.Code>
</CCCR:CommandBetaFS>
</Button.Command>
</Button>
<!--
By the button's Click event a command in VB.Net language is bound.
The launch of Calculator program is going.
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaVB : Run Calculator"
Command="{CCCR:CommandBetaVB 'System.Diagnostics.Process.Start(Context.Parameter.ToString())',
AlwaysCanExecute=True, NoExceptions=True }"
CommandParameter="calc.exe"
/>
<!--
By the button's Click event a command in the language of JS is bound.
Error handling is demonstrated. Having set NoExceptions in False,
arising exceptions (Invoker) are passed further on to the stack calls.
Information about this exeption is also available in the property LastException.
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : NoExceptions=False"
Command="{CCCR:CommandBetaJS 'fjh@#rt(y85h%$#93;',
AlwaysCanExecute=True, NoExceptions=False }"
CommandParameter="notepad.exe"
/>
<!--
By the button's Click event command in the language of JS is bound.
Error handling is demonstrated. Having installed NoExeptions in True,
arising exceptions in Invoker are intercepted, a recently emerged
exception is available in the property LastException.
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : NoExceptions=True"
Command="{CCCR:CommandBetaJS 'fjh@#rt(y85h%$#9[u43',
AlwaysCanExecute=True, NoExceptions=True }"
CommandParameter="notepad.exe"
/>
<!--
By the button's Click event command in the language of VB is bound.
Binding of a reference to the window to CommandParameter is Demonstrated.
The command closes the tied window.
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaVB : Close Window"
Command="{CCCR:CommandBetaVB 'Context.Parameter.Close()',
AlwaysCanExecute=True, NoExceptions=False}"
CommandParameter=
"{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType=local:WinMain, AncestorLevel=1}}"
/>
<!--
Command in JScript language is similar to the previous.
AlwaysCanExecute parameter was not defined, so the script
contains code to initialize ContextCommand.CanExecute
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : Close Window"
CommandParameter=
"{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType=local:WinMain, AncestorLevel=1}}"
>
<Button.Command>
<CCCR:CommandBetaJS>
<CCCR:CommandBetaJS.Code>
var v = Context;
if (v.GetCanExecute)
v.CanExecute = true;
else
v.Parameter.Close();
</CCCR:CommandBetaJS.Code>
</CCCR:CommandBetaJS>
</Button.Command>
</Button>
<!--
Command is in limited syntax of lambda expressions of C#.
Demonstrates the impossibility of invocation for the types not included in mscorlib.dll
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaCSLambda : Close... (Exception)"
Command="{CCCR:CommandBetaCSLambda 'w => w.CloseWindow()',
AlwaysCanExecute=True, NoExceptions=False}"
CommandParameter=
"{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType=local:WinMain, AncestorLevel=1}}"
/>
<TextBlock Margin="0,10,0,0"
Background="{StaticResource {x:Static SystemColors.ControlLightBrushKey}}">
ConverterBetaCSLambda: Source <-> Target</TextBlock>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Target:</TextBlock>
<!--
Converter in a limited syntax of lambda expressions in C#.
Demonstrates a possibility of a bidirectional exchange.
-->
<TextBox x:Name="m_tTxt1"
Text="{Binding
ElementName=m_tTxt2,
Path=Text,
UpdateSourceTrigger=PropertyChanged,
Converter={CCCR:ConverterBetaCSLambda Code=
'(Context.IsBack ? "Back: " : "Forward: ") + (Context.In[0]).ToString()',
NoExceptions=False}}"
/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source:</TextBlock>
<TextBox x:Name="m_tTxt2"></TextBox>
</DockPanel>
<TextBlock Margin="50,0,0,0" FontStyle="Italic">Change the contents of the textboxes</TextBlock>
</StackPanel>
<StackPanel Grid.Column="2">
<!--Example of usage of a combination of JS converter and C# validation rules.-->
<TextBlock Margin="0,0,0,0"
Background="{StaticResource {x:Static SystemColors.ControlLightBrushKey}}">
ConverterBetaJS + RuleBetaCS: TextBox <-> Slider</TextBlock>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Target:</TextBlock>
<TextBox x:Name="m_tTxt3">
<TextBox.Text>
<Binding
ElementName="m_tSlider1"
Path="Value" Mode="TwoWay"
UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource x_tCnv_JS_0_100}">
<!--Creating of a validation rule of integer values in the range [0, 100].-->
<Binding.ValidationRules>
<CCCR:RuleBetaCS>
<CCCR:RuleBetaCS.Code>
var c = (ContextRule)Context;
var n = Int32.Parse(c.In[0].ToString());
if (n < 0 || n > 100)
throw new Exception("Out of range [0; 100]!");
</CCCR:RuleBetaCS.Code>
</CCCR:RuleBetaCS>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source:</TextBlock>
<Slider x:Name="m_tSlider1" Minimum="0" Maximum="100" Value="17"></Slider>
</DockPanel>
<TextBlock Margin="50,0,0,0" FontStyle="Italic">Play with text of the textbox. Tooltips.</TextBlock>
<!--Example of usage of the combination of C# Converter and JS validation rules.-->
<TextBlock Margin="0,10,0,0"
Background="{StaticResource {x:Static SystemColors.ControlLightBrushKey}}">
ConverterBetaCS + RuleBetaJS: Slider <-> TextBox</TextBlock>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source:</TextBlock>
<TextBox x:Name="m_tTxt4"/>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Target:</TextBlock>
<Slider x:Name="m_tSlider2" Minimum="0" Maximum="100">
<Slider.Value>
<Binding
ElementName="m_tTxt4"
Path="Text" Mode="TwoWay"
ConverterParameter="Slider"
UpdateSourceTrigger="PropertyChanged"
Converter="{StaticResource x_tCnv_CS_0_100}"
ValidatesOnExceptions="True">
<!--
Creating a rule validation integer values in the range [20, 80].
Pay attention to ValidatesOnExceptions="True" and UpdateSourceTrigger="PropertyChanged".
-->
<Binding.ValidationRules>
<CCCR:RuleBetaJS>
<CCCR:RuleBetaJS.Code>
var n = int(double(Context.In[0]));
if (n < 20 || n > 80)
throw new Exception("Slider is Out of range [20; 80]!");
</CCCR:RuleBetaJS.Code>
</CCCR:RuleBetaJS>
</Binding.ValidationRules>
</Binding>
</Slider.Value>
<!---->
</Slider>
</DockPanel>
<TextBlock Margin="50,0,0,0" FontStyle="Italic">
Play with the slider (left/right edges). Tooltips.</TextBlock>
<TextBlock Margin="0,10,0,0"
Background="{StaticResource {x:Static SystemColors.ControlLightBrushKey}}">
ConverterBetaVB: Target <-> Four Sources</TextBlock>
<DockPanel HorizontalAlignment="Stretch">
<DockPanel.Resources>
<CCCR:CommandBetaCS x:Key="x_tCmdUndo"
AlwaysCanExecute="True" NoExceptions="False">
<!--
Inserting into the DockPanel’s resource a command CommandBetaCS,
which finds in the text a symbol ';' cancels input.
-->
<CCCR:CommandBetaCS.Code>
var t=(TextBox)Context.In[0];
var s=(string)Context.In[1];
if (t.Text.Contains(s)) {
Task.Factory.StartNew(() =>; {
t.Dispatcher.Invoke(
new Action(() => {
var sMsg = string.Format(
"The text contains '{0}' : \"{1}\"\n\nThe operation 'Undo' will be applied.", s, t.Text);
MessageBox.Show(sMsg, "CCCR:CommandBetaCS"); t.Undo(); }), null);
}); }
else
MessageBox.Show("Try to insert semicolons here!", "CCCR:CommandBetaCS");
</CCCR:CommandBetaCS.Code>
</CCCR:CommandBetaCS>
</DockPanel.Resources>
<TextBlock Width="50">Source 1:</TextBlock>
<TextBox x:Name="m_tTxt5a" Text="Text1">
<!--Multiple binding to pass parameters to the command "x_tCmdUndo".-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<i:InvokeCommandAction Command="{StaticResource x_tCmdUndo}">
<i:InvokeCommandAction.CommandParameter>
<MultiBinding
Converter="{CCCR:ConverterBetaCS
Code='Context.Out[0] = new object[]{Context.In[0], Context.In[1]}'}">
<Binding
RelativeSource=
"{RelativeSource FindAncestor,
AncestorType=TextBox, AncestorLevel=1}"/>
<Binding>
<Binding.Source><system:String>;</system:String></Binding.Source>
</Binding>
</MultiBinding>
</i:InvokeCommandAction.CommandParameter>
</i:InvokeCommandAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source 2:</TextBlock>
<TextBox x:Name="m_tTxt5b" Text="Text2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<!-–Demonstrated the use of TriggerAction in VB.Net.-->
<CCCR:ActionBetaVB
Code="MessageBox.Show("Try insert semicolons into the 1st textbox.", "CCCR:ActionBetaVB")"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source 3:</TextBlock>
<TextBox x:Name="m_tTxt5c" Text="Text3">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<!-–Demonstrated the use of TriggerAction in VB.Net.-->
<CCCR:ActionBetaVB
Code="MessageBox.Show("Try insert semicolons into the 1st textbox.", "CCCR:ActionBetaVB")"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Source 4:</TextBlock>
<TextBox x:Name="m_tTxt5d" Text="Text4">
<i:Interaction.Triggers>
<i:EventTrigger EventName="TextChanged" >
<CCCR:ActionBetaVB
Code="MessageBox.Show("Try insert semicolons into the 1st textbox.", "CCCR:ActionBetaVB")"/>
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
</DockPanel>
<DockPanel HorizontalAlignment="Stretch">
<TextBlock Width="50">Target:</TextBlock>
<TextBox x:Name="m_tTxt7">
<TextBox.Text>
<MultiBinding UpdateSourceTrigger="PropertyChanged">
<MultiBinding.Bindings>
<Binding ElementName="m_tTxt5a" Path="Text"/>
<Binding ElementName="m_tTxt5b" Path="Text"/>
<Binding ElementName="m_tTxt5c" Path="Text"/>
<Binding ElementName="m_tTxt5d" Path="Text"/>
</MultiBinding.Bindings>
<MultiBinding.Converter>
<CCCR:ConverterBetaVB>
<!--Example of the use of Beta VB.Net converter in multiple two-way binding-->
<CCCR:ConverterBetaVB.Code>
<system:String xml:space="preserve">
Dim c = Context
If Not c.IsBack Then
Dim f = Function(i) i.ToString()
'c.Out(0) = String.Join(" ; ", c.In.Select(Function(i) i.ToString().Trim()))
c.Out(0) = String.Join(" ; ", (Enumerable.Select(Of Object, String)(c.In, Function(i) i.ToString().Trim())))
Else
Dim a = c.In(0).ToString().Split(";"C)
c.Out(0) = IIf((a.Length > 0), a(0).Trim(), "")
c.Out(1) = IIf((a.Length > 1), a(1).Trim(), "")
c.Out(2) = IIf((a.Length > 2), a(2).Trim(), "")
c.Out(3) = IIf((a.Length > 3), a(3).Trim(), "")
End If
</system:String>
</CCCR:ConverterBetaVB.Code>
</CCCR:ConverterBetaVB>
</MultiBinding.Converter>
</MultiBinding>
</TextBox.Text>
</TextBox>
</DockPanel>
<TextBlock Margin="50,0,0,0" FontStyle="Italic">Change the contents of the textboxes</TextBlock>
</StackPanel>
</Grid>
</StackPanel>
</Grid>
Notes
I have deliberately avoided talking about TriggerAction, although the latter along with the CCCR are implemented in the library Bourlesque.Lib.Windows.CCCR (ActionAlpha, ActionBetaCS, ActionBetaVB, etc.), but even with great success they are replaced by the commands (there is an opportunity to define a CommandParameter, which is inapplicable in TriggerAction). To compile the code the following software products are required:- Microsoft .NET Framework 4
- F# PowerPack
- Microsoft Expression Blend Software Development Kit (SDK) for .NET 4
Conclusion
To what extent the material contained in this article will be in demand, will be determined by the time and by you, dear readers. I can only hope, that the time spent to create it, was not spent in vain, but it will bring at least modest moral satisfaction and a small benefit.History
2011/04/26 - Initial version.2011/05/05 - Minor cosmetic changes.