Click here to Skip to main content
15,885,365 members
Articles / Desktop Programming / WPF

CCCR: Context Commands, Converters and Rules

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
4 May 2011CPOL12 min read 19.5K   224   14  
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

CCCRContext Commands, Converters and validation Rules
CCRCommands, Converters and validation Rules (in wider sense than CCCR)
AlphaCategory of functional entities of CCCR, initialized in the main application code
BetaCategory 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.

Designations. Picture 1.

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.

Contexts. Picture 2.1. Contexts. Picture 2.2.

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.

Contexts. Picture 3.1. Contexts. Picture 3.2.

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.

Categories. Picture 4.1. Categories. Picture 4.2. Categories. Picture 4.3.

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 Invokers. 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).

Invokers. Picture 5.1. Invokers. Picture 5.2.

Invokers 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.

Invokers. Picture 6.1. Invokers. Picture 6.2. Invokers. Picture 6.3. Invokers. Picture 6.4.
Invokers. Picture 7.1. Invokers. Picture 7.2.

The implementation of the ICCCRAlpha and ICCCRBeta interfaces is shown on the following sequence diagrams of invocations.

Invokers. Picture 8. Invokers. Picture 9.

CCCR

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.

CCCR. Picture 10.1. CCCR. Picture 10.2.
CCCR. Picture 11.1. CCCR. Picture 11.2.
CCCR. Picture 12.1. CCCR. Picture 12.2.

Comparative Analysis

The following table shows the qualitative characteristics of the existing implementations of Invokers – the CCCR classes themselves are only their "clients" and have no effect on the performance and logics.

InvokersDescription
AlphaPerfomance: 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# LambdaPerfomance: 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.NetSimilarly 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.

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.

XML
<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.
C#
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.
XML
<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 ? &quot;Back: &quot; : &quot;Forward: &quot;) + (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.

Demonstration. Picture 13.

C#

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

XML
  <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 &gt; n || n &gt; 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 =&gt; 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 &lt;-&gt; 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 ? &quot;Back: &quot; : &quot;Forward: &quot;) + (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 &lt;-&gt; 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 &lt;-&gt; 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(&quot;Try insert semicolons into the 1st textbox.&quot;, &quot;CCCR:ActionBetaVB&quot;)"/>
              </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(&quot;Try insert semicolons into the 1st textbox.&quot;, &quot;CCCR:ActionBetaVB&quot;)"/>
            </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(&quot;Try insert semicolons into the 1st textbox.&quot;, &quot;CCCR:ActionBetaVB&quot;)"/>
            </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:

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.

License

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


Written By
Software Developer (Senior)
Russian Federation Russian Federation
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
-- There are no messages in this forum --