<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: yohan</title>
    <description>The latest articles on DEV Community by yohan (@zhouyongh).</description>
    <link>https://dev.to/zhouyongh</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4218216%2F212ada67-e576-46f1-8bbd-b11c61e6b6a1.jpg</url>
      <title>DEV Community: yohan</title>
      <link>https://dev.to/zhouyongh</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/zhouyongh"/>
    <language>en</language>
    <item>
      <title>Analyzing Caliburn.Micro — Part 4</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:22:34 +0000</pubDate>
      <link>https://dev.to/zhouyongh/analyzing-caliburnmicro-part-4-595j</link>
      <guid>https://dev.to/zhouyongh/analyzing-caliburnmicro-part-4-595j</guid>
      <description>&lt;p&gt;Picking up from earlier posts, we continue introducing Convention in &lt;a href="http://caliburnmicro.codeplex.com/" rel="noopener noreferrer"&gt;Caliburn.Micro&lt;/a&gt; (CM).&lt;/p&gt;

&lt;h2&gt;
  
  
  Preface
&lt;/h2&gt;

&lt;p&gt;This Caliburn.Micro series has already covered several posts. For a third-party application framework, an exhaustive walk-through of every detail is not the top priority—grasping the overall design quickly and accurately matters more. Caliburn.Micro includes many features: Action and Convention, which we have already introduced, plus Coroutine, EventAggregator (weak events), and more. Many of those are icing on the cake; EventAggregator, for example, is also covered in frameworks like Prism. CM’s core ideas are Action and Convention. This article follows that main line in detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CM Main Line
&lt;/h2&gt;

&lt;p&gt;Caliburn.Micro positions itself as a lightweight MVVM framework. In MVVM, the View and ViewModel are associated through DataContext; the View passes operations to the ViewModel via Binding and Command, and refreshes automatically when bound properties in the ViewModel change. This table compares Caliburn.Micro with a typical MVVM framework:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Typical MVVM framework&lt;/th&gt;
&lt;th&gt;Caliburn.Micro&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Automatically associate View and ViewModel (via the View’s DataContext)&lt;/td&gt;
&lt;td&gt;Not supported&lt;/td&gt;
&lt;td&gt;Supported, via NameTransformer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Command&lt;/td&gt;
&lt;td&gt;Supported; Command is recommended&lt;/td&gt;
&lt;td&gt;Not supported; Action is the alternative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Binding&lt;/td&gt;
&lt;td&gt;Supported; Binding is recommended&lt;/td&gt;
&lt;td&gt;Supported; Convention can be used as an alternative&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Validation, StringFormat, Converter, UpdateSourceTrigger&lt;/td&gt;
&lt;td&gt;Supported through Binding’s corresponding mechanisms&lt;/td&gt;
&lt;td&gt;Supported; Convention can be used as an alternative&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A simple side-by-side example:&lt;/p&gt;

&lt;p&gt;Typical MVVM framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;Command=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Path=ChangeNameCommand}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TextBox&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"{Binding Path=UserName}"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Using Caliburn.Micro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"ChangeName"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TextBox&lt;/span&gt; &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"UserName"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The corresponding ViewModel:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Zhouyongh/201108/201108311050509728.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fc2p7c86n219ksvbqj68a.png" alt="Caliburn.Micro ViewModel with matching property and method names" width="229" height="137"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;With Caliburn.Micro you only set the Name of the Button and TextBox so that it matches the corresponding property and method names on the ViewModel; CM sets up the binding and the Button method invocation automatically.&lt;/p&gt;

&lt;p&gt;That is intentional in CM’s design: make View–ViewModel interaction simpler. Set the right names and let CM do the rest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Command vs Action
&lt;/h2&gt;

&lt;p&gt;WPF defines a Command mechanism. The interface is ICommand, with Execute and CanExecute methods, and CommandParameter is available as the call argument. The Command pattern decouples the object that initiates an operation from the object that handles it—a sound idea in WPF, though the concrete implementation still has some shortcomings.&lt;/p&gt;

&lt;p&gt;Some WPF controls expose a Command property that lets you plug in an ICommand for a specific operation. Command triggering is based on Routed Events; for example, a Button triggers Command execution on OnClick. WPF implements Command on many controls such as Button, but coverage is incomplete—controls like ComboBox have no Command.&lt;/p&gt;

&lt;p&gt;A Command can respond to only one Routed Event, and for built-in controls that response is hard-wired (Click). WPF does provide CommandBindings on controls so you can attach multiple Commands, but the syntax is verbose and support for the MVVM pattern is weak. To support MVVM better, Blend introduced TriggerAction as a simpler way to attach Commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;i:Interaction.Triggers&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;i:EventTrigger&lt;/span&gt; &lt;span class="na"&gt;EventName=&lt;/span&gt;&lt;span class="s"&gt;"Click"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
              &lt;span class="nt"&gt;&amp;lt;i:ExecuteCommandAction&lt;/span&gt; &lt;span class="na"&gt;TargetCommand=&lt;/span&gt;&lt;span class="s"&gt;"LoginCommand"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
          &lt;span class="nt"&gt;&amp;lt;/i:EventTrigger&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;/i:Interaction.Triggers&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here the Command’s trigger is the Click event; when it fires, the corresponding ViewModel’s LoginCommand runs.&lt;/p&gt;

&lt;p&gt;CM’s Action is based on Blend’s TriggerAction. Its ActionMessage inherits from &lt;code&gt;TriggerAction&amp;lt;FrameworkElement&amp;gt;&lt;/code&gt;. The difference is that it does not call a Command on the ViewModel; instead it uses reflection to look up the corresponding method on the ViewModel and invoke it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action and Convention
&lt;/h2&gt;

&lt;p&gt;For convenience, a CM Action need not name a method explicitly; it matches properties and methods on the ViewModel using the control’s Name. For how Action is used in practice, see the &lt;a href="http://www.cnblogs.com/Zhouyongh/archive/2011/04/22/2018989.html" rel="noopener noreferrer"&gt;first article&lt;/a&gt;. We are discussing framework design, and designs do not appear out of thin air. ActionMessage was introduced to replace Command—let’s look at the problems that follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;CM’s Action design is deliberately foolproof: you only design the control’s Name and smart matching takes care of the rest. Because this is a “smart” mode, that intelligence is a control point. Which control properties should be smart-bound, how to keep that intelligence smart enough, and ensuring that using it does not break existing design patterns—all matter.&lt;/li&gt;
&lt;li&gt;Command and Binding are two of WPF’s highlights. After adopting CM the code is leaner, but Command and Binding seem to disappear. How do you preserve features such as Command’s CanExecute, and Binding’s Validation, StringFormat, Converter, and so on?&lt;/li&gt;
&lt;li&gt;Complexity versus flexibility. A feature-rich framework with oceans of code is intimidating; a lean framework that lacks extensibility is unsatisfying. Striking that balance is also a challenge.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To address these issues, CM introduces the concept of Convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  ElementConvention
&lt;/h2&gt;

&lt;p&gt;For how View and ViewModel are matched automatically, see the &lt;a href="http://www.cnblogs.com/Zhouyongh/archive/2011/07/20/2111716.html" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;. Here we focus on ElementConvention. In short, it was introduced to handle the “intelligence” problem: it is the description of how a Convention is processed for a concrete type. We saw earlier that with&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TextBox&lt;/span&gt; &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"UserName"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CM automatically binds the TextBox’s Text to the ViewModel’s UserName property. CM does two things here: first, based on the control type (TextBox), it decides which property of the control to bind; second, it uses the control’s name for smart matching. We will discuss the second point later; for now, see how CM handles different control types.&lt;/p&gt;

&lt;p&gt;CM uses ElementConvention as the type description for Convention processing. CM provides the static ConventionManager class that lets you manipulate ElementConventions. ElementConvention looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ElementConvention&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Type&lt;/span&gt; &lt;span class="n"&gt;ElementType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;ParameterProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;DependencyObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DependencyProperty&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetBindableProperty&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;System&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Windows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interactivity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TriggerBase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;CreateTrigger&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a TextBox, ElementType is its type (TextBox), ParameterProperty is the property it targets (Text), GetBindableProperty is a Func that returns TextProperty for a TextBox, and CreateTrigger returns the trigger event (TextChanged).&lt;/p&gt;

&lt;p&gt;ConventionManager provides default handling descriptions for several types in its static functions, for example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="n"&gt;AddElementConvention&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ButtonBase&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;ButtonBase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ContentProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DataContext"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Click"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;AddElementConvention&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextBox&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;TextBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"TextChanged"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;AddElementConvention&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;TextBlock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TextProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Text"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"DataContextChanged"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can call ConventionManager’s &lt;code&gt;AddElementConvention&lt;/code&gt; method to register a new type or change an existing ElementConvention.&lt;/p&gt;

&lt;h2&gt;
  
  
  Convention
&lt;/h2&gt;

&lt;p&gt;Having covered the Convention handling description (ElementConvention), let’s talk about the Convention process itself.&lt;/p&gt;

&lt;p&gt;The whole Convention process starts with binding View and ViewModel:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Manually or automatically, CM calls ViewModelBinder’s &lt;code&gt;Bind&lt;/code&gt; method (passing the corresponding View and ViewModel). CM uses &lt;code&gt;View.GetApplyConventions(View)&lt;/code&gt; to decide whether to apply Convention on that View. If you do not want CM’s Convention features, set the attached property &lt;code&gt;View.SetApplyConventions&lt;/code&gt; to False on the View, or set the global flag &lt;code&gt;ViewModelBinder.ApplyConventionsByDefault&lt;/code&gt; to False to disable Convention.&lt;/li&gt;
&lt;li&gt;Convention is enabled by default. CM walks the View’s VisualTree and collects every named control.&lt;/li&gt;
&lt;li&gt;Convention is applied to those named controls. There are two kinds: ActionConvention and PropertyConvention. Action means method invocation; Property means property binding.&lt;/li&gt;
&lt;li&gt;If, when calling AddElementConvention, the last argument supplied a trigger event name (Click, TextChanged, and so on), that control type can have an ActionConvention. CM matches the control’s name against all public method names on the ViewModel; if there is a match, ActionConvention is applied. For the earlier Button → ChangeName example, the method matches the corresponding ViewModel method, and the trigger is the Click event registered by AddElementConvention: &lt;code&gt;&amp;lt;Button x:Name="ChangeName" /&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;If, when calling AddElementConvention, the second argument supplied a property name, PropertyConvention applies to that control type. The method is similar: match the control’s name against all public property names on the ViewModel and, if there is a match, set up the binding.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Validation, StringFormat, Converter…
&lt;/h2&gt;

&lt;p&gt;If a control gets PropertyConvention, CM has set up the property binding for us automatically. How do Validation, StringFormat, Converter, and the rest of Binding’s features fit in?&lt;/p&gt;

&lt;p&gt;ConventionManager provides &lt;code&gt;SetBinding&lt;/code&gt; to perform PropertyConvention. Here is its code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PropertyInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FrameworkElement&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ElementConvention&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SetBinding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;viewModelType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;convention&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bindableProperty&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;convention&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;GetBindableProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bindableProperty&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;HasBinding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bindableProperty&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Binding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="nf"&gt;ApplyBindingMode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;ApplyValueConverter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bindableProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;ApplyStringFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;convention&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;ApplyValidation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;viewModelType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nf"&gt;ApplyUpdateSourceTrigger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bindableProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;property&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="n"&gt;BindingOperations&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetBinding&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bindableProperty&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;binding&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ApplyBindingMode&lt;/code&gt;, &lt;code&gt;ApplyValueConverter&lt;/code&gt;, and the rest are all Funcs you can replace with your own processing; the defaults are quite simple. In other words, the property bindings CM sets up for us are the most basic ones. CM’s PropertyConvention handling is deliberately simple. If you have already set &lt;code&gt;Text="{Binding Path=ChangeName}"&lt;/code&gt; on the property by hand, CM ignores that property. PropertyConvention only produces the most basic property binding; if you want smarter bindings, replace these Funcs with your own logic. This is also an overall CM design idea: do smart things to simplify the program, but make those operations optional, do only what is necessary, and leave room for customization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The greatest success of CM’s design is its simplicity and flexibility. It works well alongside other frameworks and can be integrated into existing code with relative ease. If you already have your own framework, try integrating CM. I will not go into CM’s remaining features here. Questions and comments are welcome—I also hope to exchange ideas with more friends. Thank you.&lt;/p&gt;

</description>
      <category>caliburnmicro</category>
      <category>mvvm</category>
      <category>wpf</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Analyzing Caliburn.Micro — Part 3</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:21:07 +0000</pubDate>
      <link>https://dev.to/zhouyongh/analyzing-caliburnmicro-part-3-p8d</link>
      <guid>https://dev.to/zhouyongh/analyzing-caliburnmicro-part-3-p8d</guid>
      <description>&lt;p&gt;Following on from the &lt;a href="http://www.cnblogs.com/Zhouyongh/archive/2011/04/22/2018989.html" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, which briefly introduced Caliburn.Micro (CM) Action, this article continues with the next CM feature: Convention.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is a Convention?
&lt;/h2&gt;

&lt;p&gt;Convention is often translated as “pact” or “agreement”—a shared rule of conduct that all parties, having reached consensus, follow. In CM, Convention is mainly used for pairing and matching. That pairing primarily means pairing the View with the ViewModel. As mentioned earlier, CM is based on the &lt;a href="http://en.wikipedia.org/wiki/Model_View_ViewModel" rel="noopener noreferrer"&gt;MVVM&lt;/a&gt; pattern. In MVVM (Model-View-ViewModel), the Model holds the data and is relatively fixed; the relationship between View and ViewModel is more fluid. Let’s briefly introduce that.&lt;/p&gt;

&lt;h2&gt;
  
  
  View-First or ViewModel-First?
&lt;/h2&gt;

&lt;p&gt;In MVVM, the ViewModel’s job is to hold the View’s state and encapsulate the Model’s data. The ViewModel evolved from the PresentationModel pattern, and it does not store every piece of View state—only state that might be modified. WPF mainly contributed two technologies that made the ViewModel practical:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Data binding. WPF provides data binding: the View binds to the ViewModel and uses the ViewModel as its DataContext. When View state stored in the ViewModel changes, the View refreshes automatically.&lt;/li&gt;
&lt;li&gt;Command. WPF provides ICommand so that when the user operates the View, the operation is passed to the ViewModel through a Command.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The View’s two main jobs—displaying data and manipulating data—are abstracted out, and View state lives in the ViewModel. Because of data binding, the View associates with the ViewModel through DataContext, and the two are loosely coupled. That is the beauty of the MVVM pattern. Back to the topic of this article: how do View and ViewModel find each other? View-First or ViewModel-First?&lt;/p&gt;

&lt;h2&gt;
  
  
  ViewModel-First
&lt;/h2&gt;

&lt;p&gt;In general, ViewModel-First is recommended. Design the ViewModel first, then generate the corresponding View with a DataTemplate. The benefit is that the View can be swapped at any time, and unit tests can mock a View. The CM framework provides ViewLocator to locate the View for a given ViewModel, with methods as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DependencyObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UIElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LocateForModel&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;DependencyObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;UIElement&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LocateForModelType&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;CM makes heavy use of &lt;code&gt;Func&lt;/code&gt; overrides for polymorphism. &lt;code&gt;LocateForModel&lt;/code&gt; and &lt;code&gt;LocateForModelType&lt;/code&gt; take a ViewModel instance or type respectively and return the corresponding View. Those names are a bit weak—&lt;code&gt;GetCorrespondingView&lt;/code&gt; would be clearer.&lt;/p&gt;

&lt;p&gt;How does locating the View from the ViewModel work? It takes two steps. First, find the View’s Type from the ViewModel. Second, create the View with reflection via &lt;code&gt;Activator.CreateInstance(Type type)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To operate on the View from the ViewModel more conveniently, CM provides IViewAware so a ViewModel can cache the corresponding View. The IViewAware interface is defined as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;IViewAware&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;AttachView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="nf"&gt;GetView&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;event&lt;/span&gt; &lt;span class="n"&gt;EventHandler&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ViewAttachedEventArgs&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;ViewAttached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;ViewAware&lt;/code&gt; is the default implementation of IViewAware. When the ViewModel and View are bound together, IViewAware’s &lt;code&gt;AttachView&lt;/code&gt; is called. You can override &lt;code&gt;OnViewAttached&lt;/code&gt; to process the View—for example, register View events (be careful of strong references). When the View is loaded into the visual tree, &lt;code&gt;ViewAware.OnViewLoaded&lt;/code&gt; is called; you can walk the View’s visual tree there.&lt;/p&gt;

&lt;p&gt;When When ViewLocator calls &lt;code&gt;LocateForModel&lt;/code&gt;, it first checks whether the ViewModel implements IViewAware and has already cached a View. If so, it returns the cached View instead of creating a new one. That brings us back to step one: how to find the View’s Type from the ViewModel.&lt;/p&gt;

&lt;h2&gt;
  
  
  NameTransformer
&lt;/h2&gt;

&lt;p&gt;CM uses NameTransformer to infer possible View types with regular expressions. The implementation works like this:&lt;/p&gt;

&lt;p&gt;NameTransformer defines ReplacePattern (the replacement rule), ReplaceValue (the replacement value), and GlobalFilterPattern (a filter rule); the inference is done with regular expressions. For example, set ReplacePattern to &lt;code&gt;ViewModel$&lt;/code&gt; and ReplaceValue to &lt;code&gt;View&lt;/code&gt;. This rule means: replace the trailing &lt;code&gt;ViewModel&lt;/code&gt; part of the string with &lt;code&gt;View&lt;/code&gt;. If the ViewModel type passed in is &lt;code&gt;Illusion.ViewModels.ShellViewModel&lt;/code&gt;, the returned type after replacement is &lt;code&gt;Illusion.ViewModels.ShellView&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;ViewLocator ships with five default rules:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Strip a trailing &lt;code&gt;View&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace a trailing &lt;code&gt;ViewModel&lt;/code&gt; with &lt;code&gt;View&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace a trailing &lt;code&gt;PageViewModel&lt;/code&gt; with &lt;code&gt;Page&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;Illusion.ViewModels.ShellViewModel&lt;/code&gt; with &lt;code&gt;Illusion.Views.ShellView&lt;/code&gt; (replace &lt;code&gt;ViewModels&lt;/code&gt; → &lt;code&gt;Views&lt;/code&gt;, &lt;code&gt;ShellViewModel&lt;/code&gt; → &lt;code&gt;ShellView&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Replace &lt;code&gt;Illusion.ViewModels.ShellPageViewModel&lt;/code&gt; with &lt;code&gt;Illusion.Views.ShellPage&lt;/code&gt; (same idea as above).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For real projects, if you follow CM’s recommended naming—&lt;code&gt;Illusion.ViewModels.ShellViewModel&lt;/code&gt; → &lt;code&gt;Illusion.Views.ShellView&lt;/code&gt;—CM will find the corresponding View automatically. If you separate View and ViewModel and do not follow those naming conventions, you must add corresponding rules to ViewLocator’s NameTransformer so it can find the View type.&lt;/p&gt;

&lt;p&gt;After ViewLocator resolves the View type, what it actually has is a string. CM still needs to turn that string into a real Type via an Assembly. CM does not scan every assembly in the AppDomain; it keeps an AssemblySource that holds the set of assemblies to search. You can override &lt;code&gt;BootStrapper.SelectAssemblies&lt;/code&gt; to return the assemblies in scope, or call AssemblySource’s &lt;code&gt;Add&lt;/code&gt; / &lt;code&gt;AddRange&lt;/code&gt; methods to add more assemblies.&lt;/p&gt;

&lt;p&gt;For example, if you use DirectoryCatalog to watch plugins and the corresponding View types live in plugins, you must add the plugin assemblies to AssemblySource—otherwise ViewLocator cannot find the View. In View–ViewModel matching, CM’s design provides some implementations and room for extension, but it is not fully automatic; you still need project-specific customization.&lt;/p&gt;

&lt;p&gt;Of course, you can skip smart matching altogether, create View and ViewModel by hand, and bind them with ViewModelBinder’s &lt;code&gt;Bind&lt;/code&gt; method.&lt;/p&gt;

&lt;h2&gt;
  
  
  View-First
&lt;/h2&gt;

&lt;p&gt;In the WP7 environment, View-First is recommended: locate the ViewModel from the View, then bind them together. The implementation and matching rules are the same as ViewModel-First and are omitted here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action Convention
&lt;/h2&gt;

&lt;p&gt;In the Action covered in the &lt;a href="http://www.cnblogs.com/Zhouyongh/archive/2011/04/22/2018989.html" rel="noopener noreferrer"&gt;previous article&lt;/a&gt;, we wrote markup like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;cm:Message.Attach=&lt;/span&gt;&lt;span class="s"&gt;"[Event MouseEnter] = [Action Show('Enter')];
                           [Event MouseLeave] = [Action Show('Leave')]"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is Action Convention: CM turns the string after Attach into the corresponding Actions. There is not much room for extension here, so I will not go into detail.&lt;/p&gt;

&lt;h2&gt;
  
  
  Property Convention
&lt;/h2&gt;

&lt;p&gt;This is one of the powerful parts of CM, and the details—including ElementConvention—are quite interesting. I’ll save them for the next post. ^_^&lt;/p&gt;

</description>
      <category>caliburnmicro</category>
      <category>mvvm</category>
      <category>wpf</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Analyzing Caliburn.Micro — Illusion = Caliburn.Micro + MEF</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:11:01 +0000</pubDate>
      <link>https://dev.to/zhouyongh/analyzing-caliburnmicro-illusion-caliburnmicro-mef-1ede</link>
      <guid>https://dev.to/zhouyongh/analyzing-caliburnmicro-illusion-caliburnmicro-mef-1ede</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;I am preparing to use &lt;a href="http://caliburnmicro.codeplex.com/" rel="noopener noreferrer"&gt;Caliburn.Micro&lt;/a&gt; together with &lt;a href="http://mef.codeplex.com/" rel="noopener noreferrer"&gt;MEF&lt;/a&gt; to build an IDE-like framework similar to VS2010. It should support plugin development, configurable menus and toolbars, and features such as Options, multi-language support, and theming. On the one hand this is a way to consolidate technical experience; on the other it can serve as a guide for friends new to WPF or interested in Caliburn.Micro.&lt;/p&gt;

&lt;p&gt;I could not think of a great name, so the project is called Illusion—I hope it is not an illusion. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbvcm6j4j63tk2vdnqv1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwbvcm6j4j63tk2vdnqv1.png" alt="Shifty" width="19" height="19"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The starting point of the project is not complicated: use MEF for plugin development, DirectoryCatalog for plugin discovery, and Export/Import to build menus, toolbars, and so on. Caliburn.Micro is an open-source framework based on the MVVM pattern. Its code is concise without being simplistic, and it is a sharp tool for enterprise application development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Implementation
&lt;/h2&gt;

&lt;p&gt;The project layout looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Zhouyongh/201107/201107181153567090.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F763wct7dbgqdj938xi4p.png" alt="Illusion solution structure" width="200" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Illusion project is the core framework. Illusion.Demo is the EXE project that uses Illusion. The AddIns folder holds several plugin projects. The solution uses the mature open-source AvalonDock for docking windows.&lt;/p&gt;

&lt;p&gt;The running program:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Zhouyongh/201107/201107181153561517.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6q6ezphl2liokmr1ilnj.png" alt="Illusion main UI with menu, toolbar, and docked windows" width="693" height="455"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project introduces two concepts: Part and Screen. As shown above, child items of the menu and toolbar are called Parts; docked windows such as Project are called Screens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configuration
&lt;/h2&gt;

&lt;p&gt;The code to add a menu item looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;MenuPart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseMenu&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WorkbenchName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WindowPart&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PreviousMenu&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WorkbenchName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CloseDocumentsPart&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ResetLayoutPart&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;MenuPart&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;ResetLayoutPart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;WorkbenchName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResetLayoutPart&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Icon&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Icons.16x16.CopyIcon"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;override&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;MenuPart&lt;/code&gt; attribute on the first line inherits from &lt;code&gt;ExportAttribute&lt;/code&gt; and is equivalent to &lt;code&gt;[Export(typeof(IMenuPart))]&lt;/code&gt;. BaseMenu and PreviousMenu specify the parent item and the preceding menu item of &lt;code&gt;ResetLayoutPart&lt;/code&gt;, which together determine where the menu item appears. When the menu is clicked, &lt;code&gt;Execute&lt;/code&gt; is called—put the menu’s action code there. You can override &lt;code&gt;CanExecute&lt;/code&gt; to decide whether the menu is enabled, and set the &lt;code&gt;IsVisible&lt;/code&gt; property to control visibility.&lt;/p&gt;

&lt;p&gt;Often the same item appears both in a menu and on a toolbar. To avoid duplicate code, a &lt;code&gt;MenuToolPart&lt;/code&gt; class is defined so that a Part can serve as both a menu item and a toolbar button.&lt;/p&gt;

&lt;p&gt;Adding a Screen is similar to adding a Part and is omitted here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Other Notes
&lt;/h2&gt;

&lt;p&gt;You can add plugin projects that export Parts, Screens, and so on.&lt;/p&gt;

&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Zhouyongh/201107/201107181153565944.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzg7zjx4or6udexoeceet.png" alt="Adding a plugin project that exports a Part" width="170" height="125"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At runtime, DirectoryCatalog watches the AddIns directory and imports all plugins.&lt;/p&gt;

&lt;p&gt;Other features—Options, multi-language, theming, etc.—are left to the code and are not described one by one here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chatter
&lt;/h2&gt;

&lt;p&gt;The Illusion project has been written on and off for over a week. Turning ideas into code is one thing; after the passion fades comes emptiness. Working behind closed doors takes patience. Over the years I have written a lot of code in my spare time and then left it to gather dust.&lt;/p&gt;

&lt;p&gt;I am uploading the code so we can discuss it. If you are interested in this area, feel free to contact me. I am not attaching any restrictive license; corrections are welcome, and so is taking whatever is useful. If it truly helps you, please leave a comment or give it a thumbs-up. As the saying goes in the jianghu:&lt;/p&gt;

&lt;p&gt;Be a brother—be decent.&lt;/p&gt;

&lt;p&gt;Download the Illusion code from &lt;a href="https://files.cnblogs.com/Zhouyongh/Illusion.rar" rel="noopener noreferrer"&gt;here&lt;/a&gt;. It uses System.Windows.Interactivity and requires Expression Blend 4 or later.&lt;/p&gt;

</description>
      <category>caliburnmicro</category>
      <category>mef</category>
      <category>wpf</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>Analyzing Caliburn.Micro — Part 2</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:09:50 +0000</pubDate>
      <link>https://dev.to/zhouyongh/analyzing-caliburnmicro-part-2-2mm1</link>
      <guid>https://dev.to/zhouyongh/analyzing-caliburnmicro-part-2-2mm1</guid>
      <description>&lt;p&gt;Following on from the &lt;a href="http://www.cnblogs.com/Zhouyongh/archive/2011/04/18/Caliburn.html" rel="noopener noreferrer"&gt;previous post&lt;/a&gt;, which briefly introduced Caliburn.Micro (CM), this article digs into CM and looks at its most interesting feature: Action.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running CM
&lt;/h2&gt;

&lt;p&gt;Download &lt;a href="http://caliburnmicro.codeplex.com/releases/view/64274" rel="noopener noreferrer"&gt;Caliburn.Micro&lt;/a&gt;, open Template, and copy &lt;code&gt;Caliburn_Micro_WPF.zip&lt;/code&gt; to &lt;code&gt;%userprofile%\Documents\Visual Studio 2010\Templates\ProjectTemplates\Visual C#\Windows&lt;/code&gt;. Create a new project in Visual Studio 2010, choose Visual C# — Windows, and you will see the Caliburn.Micro template. Create a new project named &lt;code&gt;CaliburnMicroWPF1&lt;/code&gt; as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Zhouyongh/201104/201104171718264071.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fyu8386p0nf4x2y4q4shp.png" alt="Capture of the Caliburn.Micro project template in Visual Studio 2010" width="254" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Framework folder contains the Caliburn.Micro source code. &lt;code&gt;ShellView&lt;/code&gt; is the main window; &lt;code&gt;ShellViewModel&lt;/code&gt; is the DataContext of &lt;code&gt;ShellView&lt;/code&gt;. This is a standard View–ViewModel pattern. &lt;code&gt;AppBootstrapper&lt;/code&gt; is the startup loader and lets you configure how CM is loaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrapper
&lt;/h2&gt;

&lt;p&gt;The Bootstrapper constructor looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;Bootstrapper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;IsInDesignMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;StartDesignTime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="nf"&gt;StartRuntime&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can override &lt;code&gt;StartDesignTime&lt;/code&gt; to provide design-time data for Blend and similar tools. At runtime, &lt;code&gt;StartRuntime&lt;/code&gt; is called:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;protected&lt;/span&gt; &lt;span class="k"&gt;virtual&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;StartRuntime&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InitializeWithDispatcher&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;AssemblySource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Instance&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;AddRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;SelectAssemblies&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

    &lt;span class="n"&gt;Application&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Current&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;PrepareApplication&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

    &lt;span class="nf"&gt;Configure&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;IoC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetInstance&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;IoC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAllInstances&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GetAllInstances&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;IoC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;BuildUp&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BuildUp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can override &lt;code&gt;PrepareApplication&lt;/code&gt; to listen to Application events, or override &lt;code&gt;Configure&lt;/code&gt; to set up an &lt;a href="http://en.wikipedia.org/wiki/Inversion_of_control" rel="noopener noreferrer"&gt;IOC&lt;/a&gt; (Inversion of Control) container. The three static IoC fields at the end are especially interesting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IoC&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Func&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IEnumerable&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;GetAllInstances&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;Action&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;BuildUp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;()&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt; &lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;GetInstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IoC&lt;/code&gt; is a static class that provides IOC support; its &lt;code&gt;Get&lt;/code&gt; methods look up a registered instance by type. A common approach is to define IoC as a virtual base class and let subclasses override &lt;code&gt;Get&lt;/code&gt; with their own logic. The &lt;code&gt;IoC&lt;/code&gt; class instead achieves polymorphism through three static &lt;code&gt;Func&lt;/code&gt; fields. The &lt;code&gt;AppBootstrapper&lt;/code&gt; uses MEF as the default IOC implementation. You can override Bootstrapper to use another IOC container such as Unity.&lt;/p&gt;

&lt;h2&gt;
  
  
  MEF
&lt;/h2&gt;

&lt;p&gt;MEF (Managed Extensibility Framework) was formally introduced into the .NET Framework after .NET 4.0. By attaching simple &lt;code&gt;Export&lt;/code&gt; and &lt;code&gt;Import&lt;/code&gt; attributes that describe the “consumer” and “provider” relationships between components, MEF dynamically discovers and composes components under the hood. MEF makes it much easier to develop plugin-based application systems. This article is not a detailed MEF guide; as the default IoC implementation, Caliburn.Micro simply uses MEF.&lt;/p&gt;

&lt;p&gt;Here we only adopt MEF by default—specifically, we use its &lt;code&gt;CompositionContainer&lt;/code&gt; as the IoC container. CM itself has no hard dependency on MEF. Let’s keep our eyes on CM and step into Caliburn.Micro starting from the simplest possible application.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Application
&lt;/h2&gt;

&lt;p&gt;Add a Button to &lt;code&gt;ShellView&lt;/code&gt;, drag ActionMessage onto the Button from Blend — Assets, and set the Trigger’s EventName to &lt;code&gt;Click&lt;/code&gt;. Switch to &lt;code&gt;ShellViewModel&lt;/code&gt; and add a &lt;code&gt;Show&lt;/code&gt; method:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;MessageBox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Back in &lt;code&gt;ShellView&lt;/code&gt;, set the ActionMessage’s MethodName to &lt;code&gt;Show&lt;/code&gt;, as shown below:&lt;/p&gt;

&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/Zhouyongh/201104/201104220220398276.png" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F44kz4xvrv2wjf8qgvtbl.png" alt="ActionMessage MethodName set to Show in Blend" width="348" height="241"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the program runs and you click the Button, the &lt;code&gt;Show&lt;/code&gt; method is invoked.&lt;/p&gt;

&lt;p&gt;That is the simplest possible ActionMessage usage. Let’s walk through what happens.&lt;/p&gt;

&lt;p&gt;Put simply, clicking the Button executes the method. The most primitive approach would be to put &lt;code&gt;Click="HandleBtnClick"&lt;/code&gt; in the Button’s XAML and handle the event in code-behind. Later, as MVVM became popular, you would set &lt;code&gt;ShellViewModel&lt;/code&gt; as the DataContext of &lt;code&gt;ShellView&lt;/code&gt;, provide an ICommand—&lt;code&gt;ShowCommand&lt;/code&gt;—in &lt;code&gt;ShellViewModel&lt;/code&gt;, and bind the Button with &lt;code&gt;Command="{Binding ShowCommand}"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;ActionMessage does two things here:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Match by name and automatically set &lt;code&gt;ShellViewModel&lt;/code&gt; as the DataContext of &lt;code&gt;ShellView&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use reflection to look up the method by name and invoke it.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The design is straightforward. The first step—name matching—is a distinctive CM Convention that automatically pairs &lt;code&gt;XXViewModel&lt;/code&gt; with &lt;code&gt;XXView&lt;/code&gt;. The second step looks up the &lt;code&gt;MethodInfo&lt;/code&gt; named &lt;code&gt;Show&lt;/code&gt; on the &lt;code&gt;XXViewModel&lt;/code&gt; found earlier and invokes it via reflection.&lt;/p&gt;

&lt;h2&gt;
  
  
  ActionMessage
&lt;/h2&gt;

&lt;p&gt;The design of ActionMessage is not complex. It inherits from &lt;code&gt;TriggerAction&amp;lt;FrameworkElement&amp;gt;&lt;/code&gt; and can therefore act on a FrameworkElement. At runtime the TriggerAction is attached to its corresponding FrameworkElement. ActionMessage has two important properties: MethodName and Parameters. The former is the name of the method to execute; the latter is a collection of parameters to pass in. MethodName has already been introduced above—let’s look at how Parameters works.&lt;/p&gt;

&lt;p&gt;The simplest usage is to change the ViewModel’s &lt;code&gt;Show()&lt;/code&gt; method to &lt;code&gt;Show(string message)&lt;/code&gt; and pass the argument through Parameters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;cm:ActionMessage&lt;/span&gt; &lt;span class="na"&gt;MethodName=&lt;/span&gt;&lt;span class="s"&gt;"Show"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;cm:Parameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"woo~"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/cm:ActionMessage&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;Show&lt;/code&gt; executes, the &lt;code&gt;woo~&lt;/code&gt; argument is passed in. Parameters can also carry multiple arguments; if no matching method is found, an exception is thrown. CM extends the Parameter Value as well:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Value supports binding, so you can write &lt;code&gt;Value="{Binding ElementName=XX, Path=XX}"&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;CM provides a &lt;code&gt;MessageBinder&lt;/code&gt; class to parse messages. You can use &lt;code&gt;$eventArgs&lt;/code&gt; to pass the method’s &lt;code&gt;arg&lt;/code&gt;, &lt;code&gt;$source&lt;/code&gt; to pass the bound UI element, &lt;code&gt;$dataContext&lt;/code&gt; to pass the corresponding DataContext, and so on. You can extend &lt;code&gt;MessageBinder.SpecialValues&lt;/code&gt; to add more parsing rules; by default all special tokens start with &lt;code&gt;$&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Message.Attach
&lt;/h2&gt;

&lt;p&gt;The ActionMessage markup above was written as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;i:Interaction.Triggers&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;i:EventTrigger&lt;/span&gt; &lt;span class="na"&gt;EventName=&lt;/span&gt;&lt;span class="s"&gt;"Click"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;cm:ActionMessage&lt;/span&gt; &lt;span class="na"&gt;MethodName=&lt;/span&gt;&lt;span class="s"&gt;"Show"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;cm:Parameter&lt;/span&gt; &lt;span class="na"&gt;Value=&lt;/span&gt;&lt;span class="s"&gt;"woo~"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/cm:ActionMessage&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/i:EventTrigger&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/i:Interaction.Triggers&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/Button&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the help of Blend’s Interaction, this wires the routed event Click to an ActionMessage. CM offers a more powerful Attach feature that lets you rewrite the code as:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;cm:Message.Attach=&lt;/span&gt;&lt;span class="s"&gt;"[Event Click] = [Action Show('woo~')]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Message.Attach&lt;/code&gt; is an attached property defined by CM. CM parses the value of this property to generate the corresponding Action. To make parsing easier, CM defines a small syntax. For example, &lt;code&gt;[Event Click] = [Action Show('woo~')]&lt;/code&gt; means: when the routed event Click occurs, execute the &lt;code&gt;Show&lt;/code&gt; method with the argument &lt;code&gt;woo~&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Special characters: square brackets &lt;code&gt;[]&lt;/code&gt; denote an object; &lt;code&gt;[Event Click]&lt;/code&gt; has a first parameter that says the object kind is Event and a second that names it Click; the equals sign &lt;code&gt;=&lt;/code&gt; connects the Event to the ActionMessage; parentheses &lt;code&gt;()&lt;/code&gt; hold the arguments. Semicolons separate multiple Event/ActionMessage pairs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;cm:Message.Attach=&lt;/span&gt;&lt;span class="s"&gt;"[Event MouseEnter] = [Action Show('Enter')];
                           [Event MouseLeave] = [Action Show('Leave')]"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This declares two Event/ActionMessage pairs.&lt;/p&gt;

&lt;p&gt;When passing arguments inside parentheses you can use more than plain strings—you can also use bindings:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;TextBlock&lt;/span&gt; &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"txtBlock"&lt;/span&gt; &lt;span class="na"&gt;Text=&lt;/span&gt;&lt;span class="s"&gt;"woo~"&lt;/span&gt;&lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;cm:Message.Attach=&lt;/span&gt;&lt;span class="s"&gt;"[Event Click] = [Action Show(txtBlock.Text)]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Digging into Action
&lt;/h2&gt;

&lt;p&gt;We have been talking about ActionMessage throughout; here is a detailed look at CM’s Action mechanism.&lt;/p&gt;

&lt;p&gt;In short, Action means: when event A occurs, execute method C on object B.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;ActionMessage inherits from &lt;code&gt;TriggerAction&amp;lt;FrameworkElement&amp;gt;&lt;/code&gt;; TriggerAction comes from Blend’s Interactivity. When event A fires, ActionMessage’s &lt;code&gt;Invoke&lt;/code&gt; method is called. Blend’s TriggerBase is not the focus of this article—only note that Blend guarantees the “event A” step.&lt;/li&gt;
&lt;li&gt;When the ActionMessage is created, the MethodName property identifies method C. So the key to the whole ActionMessage is how to find object B.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The Caliburn.Micro framework is based on MVVM; if MVVM holds no interest for you, feel free to ignore Caliburn.Micro. In MVVM (Model-View-ViewModel), operations on the View are passed to the VM through Commands. Object B here is the ViewModel. CM represents the target object B as Target; by default, Target equals the control’s DataContext. CM provides the &lt;code&gt;Action.Target&lt;/code&gt; attached property to set Target explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;ListBox&lt;/span&gt; &lt;span class="na"&gt;x:Name=&lt;/span&gt;&lt;span class="s"&gt;"lstBox"&lt;/span&gt;  &lt;span class="na"&gt;SelectionMode=&lt;/span&gt;&lt;span class="s"&gt;"Extended"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ListBoxItem&amp;gt;&lt;/span&gt;123&lt;span class="nt"&gt;&amp;lt;/ListBoxItem&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ListBoxItem&amp;gt;&lt;/span&gt;123&lt;span class="nt"&gt;&amp;lt;/ListBoxItem&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;ListBoxItem&amp;gt;&lt;/span&gt;123&lt;span class="nt"&gt;&amp;lt;/ListBoxItem&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/ListBox&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;Button&lt;/span&gt; &lt;span class="na"&gt;cm:Action.Target=&lt;/span&gt;&lt;span class="s"&gt;"{Binding ElementName=lstBox}"&lt;/span&gt; &lt;span class="na"&gt;Focusable=&lt;/span&gt;&lt;span class="s"&gt;"False"&lt;/span&gt;
        &lt;span class="na"&gt;cm:Message.Attach=&lt;/span&gt;&lt;span class="s"&gt;"[Event Click] = [Action SelectAll]"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sets the Action’s Target to the ListBox; when the Button is clicked, the ListBox’s &lt;code&gt;SelectAll&lt;/code&gt; method is called.&lt;/p&gt;

&lt;p&gt;Here &lt;code&gt;cm:Action.Target&lt;/code&gt; is equivalent to &lt;code&gt;DataContext="{Binding …}"&lt;/code&gt;: when Target is set, CM also sets the control’s DataContext. Sometimes you do not want to change the control’s DataContext. For that, Action provides TargetWithoutContext, which sets Target without syncing the control’s DataContext.&lt;/p&gt;

&lt;p&gt;During Action execution, ActionMessage supplies an ActionExecutionContext as the execution context. That context is mainly used by another important feature—coroutines—and will not be covered in detail here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This article introduced CM’s Action mechanism. Action is a highlight of the CM design: concise and powerful. Around Action, CM also provides other features such as coroutines. I hope everyone will grasp Action solidly as the starting point for working with CM.&lt;/p&gt;

</description>
      <category>caliburnmicro</category>
      <category>mvvm</category>
      <category>wpf</category>
      <category>dotnet</category>
    </item>
    <item>
      <title>CODESYS AI Practice — Unit Testing</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:59:57 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-ai-practice-unit-testing-h11</link>
      <guid>https://dev.to/zhouyongh/codesys-ai-practice-unit-testing-h11</guid>
      <description>&lt;p&gt;Environment: CODESYS V3.5 SP17 | SoftMotion Win 64 | Windows + PowerShell 5.1&lt;/p&gt;

&lt;p&gt;The previous post, &lt;em&gt;CODESYS AI Practice — Online Connection&lt;/em&gt;, downloaded the program into the soft PLC so we could start/stop it and read/write variables.&lt;/p&gt;

&lt;p&gt;This post lets &lt;strong&gt;AI write unit tests for CODESYS&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The goal is a one-sentence ask: the AI writes the unit tests itself, imports the project, produces a red/green report, and so on. The approach also supports regression and other standard unit-testing scenarios.&lt;/p&gt;

&lt;h2&gt;
  
  
  Choosing an approach
&lt;/h2&gt;

&lt;p&gt;Unit testing in CODESYS is not a new problem. Existing solutions fall into a few families:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;What it tests&lt;/th&gt;
&lt;th&gt;Simulation / real hardware&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host-side pytest driving read/write&lt;/td&gt;
&lt;td&gt;Black-box interface&lt;/td&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;td&gt;Assertions sit far from the field; timing is nondeterministic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Matiec offline compile checks&lt;/td&gt;
&lt;td&gt;Pure logic&lt;/td&gt;
&lt;td&gt;No runtime connection&lt;/td&gt;
&lt;td&gt;Does not cover axis control or online state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CoUnit / UniTest (library into the PLC)&lt;/td&gt;
&lt;td&gt;White-box&lt;/td&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;td&gt;Requires installing a library; most community libraries are unmaintained&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CODESYS Test Manager (3S)&lt;/td&gt;
&lt;td&gt;White-box&lt;/td&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;td&gt;Commercial; often limited in OEM environments&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hand-rolled ST asserts + host collection&lt;/td&gt;
&lt;td&gt;White-box&lt;/td&gt;
&lt;td&gt;Either&lt;/td&gt;
&lt;td&gt;You maintain the protocol; PLC side has zero dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I settled on: assertions live with the logic; no PLC-side dependencies; host gets a unified result stream; design modeled on xunit.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhivh6l4syza7emdz47s2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fhivh6l4syza7emdz47s2.png" alt="Test code compiles with the application; assertions run in the PLC cycle; the host reads results back" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test code is ordinary ST. It compiles and downloads with the application. Assertions and the logic under test run in the same cycle over the same variables. The host only starts a round and reads results back.&lt;/p&gt;

&lt;p&gt;A single round:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The suite is attached to a cyclic task via &lt;code&gt;PRG_Tests&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Each case runs as &lt;code&gt;Setup&lt;/code&gt; → assertions → &lt;code&gt;TearDown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Assertions write results into the case table in &lt;code&gt;GVL_Test&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;When all suites finish, a done flag is set.&lt;/li&gt;
&lt;li&gt;The host polls the done flag, reads counts and cases, and generates a report.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  xunit mapping
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;suite&lt;/strong&gt; → an &lt;code&gt;FB_&amp;lt;Unit&amp;gt;_Test&lt;/code&gt; function block&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;test&lt;/strong&gt; → a &lt;code&gt;Test_*&lt;/code&gt; method on the suite&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;assert&lt;/strong&gt; → calls such as &lt;code&gt;ass.AssertEquals_*&lt;/code&gt; / &lt;code&gt;AssertTrue&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;fixture&lt;/strong&gt; → &lt;code&gt;Setup&lt;/code&gt; / &lt;code&gt;TearDown&lt;/code&gt; plus the suite's &lt;code&gt;VAR&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Capabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Test organization&lt;/td&gt;
&lt;td&gt;suite / test / assert / fixture&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assertions&lt;/td&gt;
&lt;td&gt;Numeric, boolean, integer; &lt;code&gt;Skip&lt;/code&gt; supported&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run targets&lt;/td&gt;
&lt;td&gt;SoftMotion / Control Win, simulation and real hardware&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Results&lt;/td&gt;
&lt;td&gt;Text report, JUnit XML, exit codes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continuous runs&lt;/td&gt;
&lt;td&gt;Reset + read-back; multi-round runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Targeting&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;g_sFilter&lt;/code&gt; for one suite; &lt;code&gt;g_sFirstFailure&lt;/code&gt; for first failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Dependencies&lt;/td&gt;
&lt;td&gt;Zero on the PLC side; host reuses codesys-connect / codesys-sync&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Project, CODESYS, and run-target versions must match.&lt;/li&gt;
&lt;li&gt;SoftMotion Win V3 x64 device type is &lt;code&gt;4102/0000 0004&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;run&lt;/code&gt; only logs in; call &lt;code&gt;download&lt;/code&gt; to load new logic.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;PRG_Tests&lt;/code&gt; must sit on a cyclic task and &lt;code&gt;g_uiSuitesTotal&lt;/code&gt; must match the suite count.&lt;/li&gt;
&lt;li&gt;One &lt;code&gt;Assert&lt;/code&gt; records one case.&lt;/li&gt;
&lt;li&gt;Prefer incremental asserts on multi-round runs.&lt;/li&gt;
&lt;li&gt;Never commit passwords; use &lt;code&gt;-UserFile&lt;/code&gt; or environment variables.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>codesys</category>
      <category>ai</category>
      <category>plc</category>
      <category>testing</category>
    </item>
    <item>
      <title>CODESYS AI Practice — Online Connection</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:58:46 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-ai-practice-online-connection-25f9</link>
      <guid>https://dev.to/zhouyongh/codesys-ai-practice-online-connection-25f9</guid>
      <description>&lt;p&gt;Environment: CODESYS V3.5 SP17 | SoftMotion Win 64 | Windows + PowerShell 5.1&lt;/p&gt;

&lt;p&gt;The previous post, &lt;em&gt;CODESYS AI Practice — Project Sync&lt;/em&gt;, solved “project ⇄ text ⇄ AI”, but the program still only lived in the project.&lt;/p&gt;

&lt;p&gt;This post continues: let AI download the program to a soft PLC, start and stop it, and read and write variables.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;One sentence: give AI an online channel into the controller.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzkjnfdo7yyhamo9ouq78.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fzkjnfdo7yyhamo9ouq78.png" alt="Online loop: connect, download, start/stop, read/write" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrnctq8lpsv85nb8jy3h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvrnctq8lpsv85nb8jy3h.png" alt="AI → Script Engine → soft PLC" width="800" height="262"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The path is: &lt;strong&gt;AI → Script Engine → soft PLC&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The AI plans actions. The script engine (IronPython inside CODESYS) executes them against the runtime. The soft PLC runs the application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Capabilities
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Connect&lt;/td&gt;
&lt;td&gt;Open an online session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Download&lt;/td&gt;
&lt;td&gt;Load the compiled application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Start / Stop&lt;/td&gt;
&lt;td&gt;Control run state&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Read / Write&lt;/td&gt;
&lt;td&gt;Access live variables&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Download is a separate step from run&lt;/li&gt;
&lt;li&gt;Variable paths follow the controller’s naming (GVL / program scope)&lt;/li&gt;
&lt;li&gt;Simulation mode works without physical hardware&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the online closed loop for the host/controller series.&lt;/p&gt;

</description>
      <category>codesys</category>
      <category>ai</category>
      <category>plc</category>
      <category>automation</category>
    </item>
    <item>
      <title>CODESYS AI Practice — Import/Export Files</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:48:06 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-ai-practice-importexport-files-18j7</link>
      <guid>https://dev.to/zhouyongh/codesys-ai-practice-importexport-files-18j7</guid>
      <description>&lt;p&gt;In CODESYS v3, code lives inside the project — a binary file. AI cannot open or edit it.&lt;/p&gt;

&lt;p&gt;In the AI era we need a way for AI to operate on the files inside the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Close the loop: project ⇄ text ⇄ AI.&lt;/p&gt;

&lt;p&gt;Export POUs to text, let the AI edit them, import them back into the project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Constraints
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;CODESYS must stay the source of truth for the project structure&lt;/li&gt;
&lt;li&gt;Import must not corrupt an existing project&lt;/li&gt;
&lt;li&gt;Prefer a small, scriptable tool over a heavy IDE plugin&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Approach
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Export selected POUs (declaration + implementation) to plain text files&lt;/li&gt;
&lt;li&gt;Edit the text (AI or human)&lt;/li&gt;
&lt;li&gt;Import: backup, compare, write back, save&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Creating or deleting POUs is not supported in this pass — only updating existing ones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fexb4mp9cr5i6a6a87qpc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fexb4mp9cr5i6a6a87qpc.png" alt="Export, edit, import loop" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjll1915tjww1nmwiradw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjll1915tjww1nmwiradw.png" alt="Exported project tree" width="799" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Import pipeline
&lt;/h2&gt;

&lt;p&gt;Import does four things before touching the live project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Backup&lt;/strong&gt; — snapshot first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare&lt;/strong&gt; — diff bodies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write back&lt;/strong&gt; — set POU code&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save&lt;/strong&gt; — persist the project&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8n5llu6k8gbtcbkg2mo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fs8n5llu6k8gbtcbkg2mo.png" alt="Import pipeline: backup, compare, write back, save" width="800" height="240"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Update only: creating or deleting POUs is not supported in this version&lt;/li&gt;
&lt;li&gt;Declarations and implementations are paired per POU&lt;/li&gt;
&lt;li&gt;After import, rebuild in CODESYS to verify&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Repository and tooling details follow the same codesys-sync / codesys-connect series used elsewhere on this blog.&lt;/p&gt;

</description>
      <category>codesys</category>
      <category>ai</category>
      <category>plc</category>
      <category>automation</category>
    </item>
    <item>
      <title>CODESYS AI Practice — Project Sync</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:47:06 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-ai-practice-project-sync-4jea</link>
      <guid>https://dev.to/zhouyongh/codesys-ai-practice-project-sync-4jea</guid>
      <description>&lt;p&gt;Environment: CODESYS V3.5 SP17 / SP22 | Windows + PowerShell 5.1&lt;/p&gt;

&lt;p&gt;The previous post, &lt;em&gt;CODESYS AI Practice — Import/Export Files&lt;/em&gt;, closed the “project ⇄ text ⇄ AI” loop, but it was still a code-editing-only tool. This post completes the picture by turning it into a tool that operates on the project itself — CRUD for project objects.&lt;/p&gt;

&lt;p&gt;Same approach as before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What “project sync” means here
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Export project objects (POUs, DUTs, GVLs) to text&lt;/li&gt;
&lt;li&gt;Edit text (AI)&lt;/li&gt;
&lt;li&gt;Import back into the project with safety rails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The loop is the same: export → edit → import → sync.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6rijnac2lmvje4wnoqqb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6rijnac2lmvje4wnoqqb.png" alt="Export, edit, import loop" width="799" height="213"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Beyond code edit
&lt;/h2&gt;

&lt;p&gt;Earlier import/export only rewrote existing code bodies. This version can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create new objects&lt;/li&gt;
&lt;li&gt;Delete objects&lt;/li&gt;
&lt;li&gt;Rename objects&lt;/li&gt;
&lt;li&gt;Sync declarations with implementations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;…while still treating the CODESYS project as the single source of truth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Safety
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Backup before write-back&lt;/li&gt;
&lt;li&gt;Diff before apply&lt;/li&gt;
&lt;li&gt;Save after successful import&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Project sync makes the text side a full peer of the binary project: AI can change structure, not only bodies — with the same export/edit/import discipline as the earlier posts in this series.&lt;/p&gt;

</description>
      <category>codesys</category>
      <category>ai</category>
      <category>plc</category>
      <category>automation</category>
    </item>
    <item>
      <title>CODESYS Applied Practice — Axes (Part 1)</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:39:52 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-applied-practice-axes-part-1-3l6h</link>
      <guid>https://dev.to/zhouyongh/codesys-applied-practice-axes-part-1-3l6h</guid>
      <description>&lt;p&gt;Environment: CODESYS V3.5 SP22 | Target: CODESYS Control Win V3 x64 simulation&lt;/p&gt;

&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;The previous lesson built a minimal host/controller loop: three axes that were hand-rolled — &lt;code&gt;FB_SimAxis&lt;/code&gt; parsed commands and climbed position itself. The motion was real; the axes were fake.&lt;/p&gt;

&lt;p&gt;This lesson meets a real axis.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is an axis?
&lt;/h2&gt;

&lt;p&gt;Start from motion control. In short: an axis is the motion counterpart of a control object — setpoints and status on one side, drive + motor + encoder on the other, bound by mapping.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe1tq93hdqow4pg6c7sxe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fe1tq93hdqow4pg6c7sxe.png" alt="Two halves of one axis: control and motion, bound by mapping" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  MC function blocks
&lt;/h2&gt;

&lt;p&gt;Motion control FBs share a common timing shape: execute edge, busy, done, error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4gbcskjg4cf0tmgbe2bg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F4gbcskjg4cf0tmgbe2bg.png" alt="Common MC function-block timing: normal completion and abort" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  CiA 402
&lt;/h2&gt;

&lt;p&gt;Drives speak a state machine: Not ready to switch on → Switch on disabled → Ready to switch on → Switched on → Operation enabled.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7mibab7xgvcbqgqlild.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fu7mibab7xgvcbqgqlild.png" alt="CiA 402 main path" width="799" height="373"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Three boundaries
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flmstiu2ne9tfdt1ezdqg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flmstiu2ne9tfdt1ezdqg.png" alt="Three boundaries, three contracts" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Motion API (MC_*)&lt;/li&gt;
&lt;li&gt;Axis object (state + setpoints)&lt;/li&gt;
&lt;li&gt;Drive profile (CiA 402 / fieldbus)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Same program, swap the backend
&lt;/h2&gt;

&lt;p&gt;Names stay fixed; the implementation can be simulated or real.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsqw37xts9pegx1fscp1h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsqw37xts9pegx1fscp1h.png" alt="Same program: swap the backend, keep the names" width="799" height="253"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Logical axis vs physical axis&lt;/li&gt;
&lt;li&gt;Mapping binds them&lt;/li&gt;
&lt;li&gt;MC timing is consistent across FBs&lt;/li&gt;
&lt;li&gt;CiA 402 is the drive language&lt;/li&gt;
&lt;li&gt;Same program can run on sim or hardware&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codesys</category>
      <category>plc</category>
      <category>motion</category>
      <category>softmotion</category>
    </item>
    <item>
      <title>CODESYS Applied Practice — Host/Controller Closed Loop</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:38:57 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-applied-practice-hostcontroller-closed-loop-5edk</link>
      <guid>https://dev.to/zhouyongh/codesys-applied-practice-hostcontroller-closed-loop-5edk</guid>
      <description>&lt;p&gt;This post closes a practical loop: a minimal host/controller setup you can actually run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;Host PC talks to a soft PLC (CODESYS Control Win). The application exposes axes and variables. The host starts, stops, and exchanges data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4ofzgcwkwujsh7jdtai.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz4ofzgcwkwujsh7jdtai.png" alt="Host ↔ controller closed loop" width="800" height="267"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Pieces
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Piece&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Host PC&lt;/td&gt;
&lt;td&gt;UI / scripts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CODESYS runtime&lt;/td&gt;
&lt;td&gt;Soft PLC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Application&lt;/td&gt;
&lt;td&gt;PLC_PRG + axes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Device setup
&lt;/h2&gt;

&lt;p&gt;Create a standard project, add SoftMotion Win (or Control Win), define the program and axis objects, then compile and download.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wy3bu9dj3sn2ud1mrwr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F5wy3bu9dj3sn2ud1mrwr.png" alt="New Standard Project dialog" width="303" height="519"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9nseg1xkjxthneutvke.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fw9nseg1xkjxthneutvke.png" alt="Device tree" width="800" height="724"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qcahrp2q2qow0q2cn02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F6qcahrp2q2qow0q2cn02.png" alt="Application structure" width="799" height="638"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36q6234ecfpkvv7v00oi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F36q6234ecfpkvv7v00oi.png" alt="Online view" width="800" height="386"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz73l1bkt4qxxtoum33i7.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz73l1bkt4qxxtoum33i7.gif" alt="Running animation" width="720" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Simulated axes
&lt;/h2&gt;

&lt;p&gt;The sample uses &lt;code&gt;FB_SimAxis&lt;/code&gt;: the block parses commands and ramps position. The motion is real in the program; the “axis” is simulated. Good enough to prove the host/controller loop before touching hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you get
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Download and start from the host&lt;/li&gt;
&lt;li&gt;Read/write variables&lt;/li&gt;
&lt;li&gt;A base for the later CODESYS AI and axes posts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the closed loop the rest of the CODESYS series builds on.&lt;/p&gt;

</description>
      <category>codesys</category>
      <category>plc</category>
      <category>automation</category>
      <category>softplc</category>
    </item>
    <item>
      <title>CODESYS Features — EtherCAT Firmware Update</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:31:27 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-features-ethercat-firmware-update-28n7</link>
      <guid>https://dev.to/zhouyongh/codesys-features-ethercat-firmware-update-28n7</guid>
      <description>&lt;p&gt;This post explains how to update EtherCAT slave firmware in a CODESYS system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Firmware
&lt;/h2&gt;

&lt;p&gt;EtherCAT stations split into masters (controllers) and slaves (drives, and so on). Update methods differ.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Kind&lt;/th&gt;
&lt;th&gt;How&lt;/th&gt;
&lt;th&gt;Who runs it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Master application (&lt;code&gt;.app&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;IDE download, or replace the boot application&lt;/td&gt;
&lt;td&gt;CODESYS runtime&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slave firmware&lt;/td&gt;
&lt;td&gt;FoE / vendor tools over EtherCAT&lt;/td&gt;
&lt;td&gt;Master + slave&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsts5yzua0x6hvtkcwd9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwsts5yzua0x6hvtkcwd9.png" alt="Enable Expert Settings on the slave" width="800" height="521"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  FoE upgrade states
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq9ifm3xr59pbfrncf1e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fiq9ifm3xr59pbfrncf1e.png" alt="State transition during a FoE upgrade" width="800" height="320"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Idle → FoE connect → Transfer firmware → Checksum verify → Reboot / apply.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three-party sequence
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fokho12ayqzr8zv2flq0q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fokho12ayqzr8zv2flq0q.png" alt="Three-party sequence" width="800" height="280"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Master/IDE, controller, and slave coordinate the transfer and restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  Controller-side state machine
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8xblm99ywiu7r9xqn588.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F8xblm99ywiu7r9xqn588.png" alt="Controller-side firmware upgrade state machine" width="800" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IDLE → PREPARE → TRANSFER → VALIDATE → APPLY.&lt;/p&gt;

&lt;h2&gt;
  
  
  Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Match firmware to the exact slave model&lt;/li&gt;
&lt;li&gt;Keep the bus stable during transfer&lt;/li&gt;
&lt;li&gt;Plan for reboot: axes will drop until the slave returns&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>codesys</category>
      <category>plc</category>
      <category>ethercat</category>
      <category>firmware</category>
    </item>
    <item>
      <title>CODESYS Features — Download and Startup</title>
      <dc:creator>yohan</dc:creator>
      <pubDate>Sat, 31 Oct 2026 07:30:37 +0000</pubDate>
      <link>https://dev.to/zhouyongh/codesys-features-download-and-startup-29b0</link>
      <guid>https://dev.to/zhouyongh/codesys-features-download-and-startup-29b0</guid>
      <description>&lt;p&gt;Building CODESYS applications means download and start are unavoidable. This post digs one layer inward and walks the chain from compile to run.&lt;/p&gt;

&lt;p&gt;You can also download and update from the command line without opening the CODESYS IDE.&lt;/p&gt;

&lt;h2&gt;
  
  
  The run chain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Compile
&lt;/h3&gt;

&lt;p&gt;After the IDE compiles, you get two files:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.app&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The application image: compiled target code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;.crc&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;A small checksum file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1cxvsz4bfi9c6dnmdrk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fd1cxvsz4bfi9c6dnmdrk.png" alt="Compile → Login → Download → Run" width="800" height="183"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Login vs Download
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8jf38dqqcmbwzmq4z19.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fq8jf38dqqcmbwzmq4z19.png" alt="Login vs Download: what happens inside the controller after you click" width="799" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Login&lt;/strong&gt; opens the online connection, compares the application signature, and builds an online-change plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download&lt;/strong&gt; transfers the &lt;code&gt;.app&lt;/code&gt; via the gateway protocol on port 1217, writes the image into the controller, and applies the restart policy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Controller startup
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fol9s9cmpp6sbs4u3d9j3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fol9s9cmpp6sbs4u3d9j3.png" alt="Controller startup path" width="799" height="305"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Boot → Runtime services → Select boot application → Start (enter RUN).&lt;/p&gt;

&lt;h3&gt;
  
  
  IDE download vs manual update
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3d2x0nu6s06bzb5hsdi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fm3d2x0nu6s06bzb5hsdi.png" alt="IDE download vs manual update" width="799" height="333"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;IDE download is for the development loop. Manual update (copy &lt;code&gt;.app&lt;/code&gt;/&lt;code&gt;.crc&lt;/code&gt;, set boot entry, restart) is for production field updates.&lt;/p&gt;

</description>
      <category>codesys</category>
      <category>plc</category>
      <category>automation</category>
      <category>industrial</category>
    </item>
  </channel>
</rss>
