Contents
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.
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.
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.
The subject of this section is about the implementation of CCCR in the context
of using it by the end-user.
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.
|
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.
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.
Earlier in the article there were described mechanisms for implementation
of CCCR in the bond
Context
-
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.
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
|
|
The table rates rather theoretical data, so that there may be some divergences
with the practical results.
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!
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 =
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)
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)
m_tCommandBetaJS1 =
new CommandBetaJS (
@"MessageBox.Show(!String.IsNullOrWhiteSpace(Context.Parameter) ?
Context.Parameter : ""Beta1-(NO PARAMs)"")") { AlwaysCanExecute = true };
return m_tCommandBetaJS1; } }
XAML
<Window.Resources>
-->
<CCCR:CommandBetaJS
x:Key="x_tCmd_MsgBox"
Code="MessageBox.Show(Context.Parameter)"
AlwaysCanExecute="True">
</CCCR:CommandBetaJS>
-->
<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>
-->
<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 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>
-->
<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>
-->
<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}"
/>
-->
<Button
x:Name="m_tBtnAlpha2"
HorizontalContentAlignment="Left"
Content="Code / CommandAlpha : Show -//- (Shorter syntax)"
CommandParameter=" "
Command="{x:Static local:WinMain.CommandAlpha2}"
/>
-->
<Button
x:Name="m_tBtnBetaJS1"
HorizontalContentAlignment="Left"
Content="Code / CommandBetaJS : Show -//-//-"
CommandParameter=" "
Command="{x:Static local:WinMain.CommandBetaJS1}"
/>
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : Show Message Box"
Command="{StaticResource x_tCmd_MsgBox}"
CommandParameter="Command Parameter ABRACADABRA"
>
</Button>
-->
<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>
-->
<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"
/>
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : NoExceptions=False"
Command="{CCCR:CommandBetaJS 'fjh@#rt(y85h%$#93;',
AlwaysCanExecute=True, NoExceptions=False }"
CommandParameter="notepad.exe"
/>
-->
<Button
HorizontalContentAlignment="Left"
Content="XAML / CommandBetaJS : NoExceptions=True"
Command="{CCCR:CommandBetaJS 'fjh@#rt(y85h%$#9[u43',
AlwaysCanExecute=True, NoExceptions=True }"
CommandParameter="notepad.exe"
/>
-->
<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}}"
/>
-->
<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>
-->
<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>
-->
<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">
-->
<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}">
-->
<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>
-->
<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">
-->
<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">
-->
<CCCR:CommandBetaCS.Code>
var t=(TextBox)Context.In[0];
var s=(string)Context.In[1];
if (t.Text.Contains(s)) {
Task.Factory.StartNew(() =>
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:
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.