Skip to content

Extend Visual Studio’s IntelliSense for TwinCAT HMI

The information in this post are usable with:

As you already know the TwinCAT HMI designer is a split view implementation. On one side a WYSIWYG-editor based on CEF (so-called Designer) is provided, on the other side the plain code editor of Visual Studio is usable to modify your HMI-implementation directly. To add new controls to your HMI you have multiple ways to reach your goal:

  • use the Toolbox and Drag&Drop one or more controls to the WYSIWYG -editor
  • use the code editor and just write down the plain HTML-code

The current shipped TwinCAT HMI 1.10 release is equipment with few IntelliSense modifications, e.g. a list of specific attributes is provided based on the current tchmi-type and should make the edit a little bit faster:

This approach is nice but with additional IntelliSense improvements, the coding process to add new TwinCAT HMI control instances can be more accelerated. Currently, we have to add a minimum set of four attributes (only to get the control rendered in the WYSIWYG-editor):

id The id attribute specifies a unique id for an HTML element (the value must be unique within the HTML document). [Online]
data-tchmi-type Type of the new control. [Beckhoff InfoSys]
data-tchmi-widthThe width of the new control. [Beckhoff InfoSys]
data-tchmi-heightThe height of the new control. [Beckhoff InfoSys]

Implementation to improve control creation

To get an idea how IntelliSense can be extended individually just check available sourcecode hosted on GitHub, search for IHtmlCompletionListProvider and dive into some code examples. During the writting of this post in total 38 code examples can be found, the recently indexed example is the sourcecode of this post.

The barebone to extend IntelliSense is based on few lines of code:

namespace IntellisenseCreateControl
{
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Utilities;
    using Microsoft.Html.Editor.Completion;
    using Microsoft.Html.Editor.Completion.Def;
    using Microsoft.Web.Core.ContentTypes;
    using System.ComponentModel.Composition;
    
    [Order(Before = "default")]
    [HtmlCompletionProvider(CompletionTypes.Attributes, CompletionName)]
    [ContentType(HtmlContentTypeDefinition.HtmlContentType)]
    public class CreateControlCompletion : IHtmlCompletionListProvider
    {
        [Import]
        internal SVsServiceProvider ServiceProvider = null;

        public const string CompletionName = "div";

        protected static ReadOnlyCollection<HtmlCompletion> Empty { get; } 
             = new ReadOnlyCollection<HtmlCompletion>(new HtmlCompletion[0]);

        public IList<HtmlCompletion> GetEntries(HtmlCompletionContext ctx)
        {
            var ctrlList = new List<HtmlCompletion>();

           /*
            * Create and fill IntelliSense list...
		    */

            return new List<HtmlCompletion>(ctrlList);
        }        
    }
}

It is not that easy to find any good documentation of all of this, especially the attributes above the class declaration or the interface IHtmlCompletionListProvider. The best way to learn more about this is to code, adding breakpoints, debug, check internal states, etc. After a while, you will get a feeling about all of this. The [Import]-call is MEF magic (Managed Extensibility Framework) [Online]. Here, the call allows us the access the VisualStudio service provider to query any accessible service, or to access a valid EnvDTE.DTE instance which we need to query a large set of information, e.g. the associated project of the current document (check Utilities.cs, Method GetProjectContainingFile(..)).

With the attribute HtmlCompletionProvider we register the IntelliSense support for specific editor areas, let us call them trigger. Any HTML-document contains more or less entities, i.e. attributes, childs, etc. In our case we enable a trigger for attributes. For example, when you code “<div “ our implementation will be triggered, and GetEntries(..) will be called to fill a list of HtmlCompletion instances.

By choosing one of the entries, individual code will be added to the code editor. The div-tag is not finished after insert, you have to finalize it by entering “>”. In case you already know, which attributes you like to append try to add them with the already shipped IntelliSense support described at the beginning of this post. The result can look like this and as you see, a new control is created within the WYSIWYG-editor:

The full implementation of the GetEntries-method is:

[Order(Before = "default")]
[HtmlCompletionProvider(CompletionTypes.Attributes, CompletionName)]
[ContentType(HtmlContentTypeDefinition.HtmlContentType)]
public class CreateControlCompletion : IHtmlCompletionListProvider
{
  // [snip]

  public IList<HtmlCompletion> GetEntries(HtmlCompletionContext ctx)
  {
    var hmiPrj = GetHmiProject(ctx);
    if (hmiPrj == null) return Empty;

    var attrs = ctx.Element.Attributes;
    if (attrs.Get("data-tchmi-type") != null) return null;

    var ctrlProvider = hmiPrj.GetControlProvider();
    if (ctrlProvider?.AvailableControls == null) return Empty;

    var ctrlList = new List<HtmlCompletion>();

    foreach (var it in ctrlProvider.AvailableControls)
    {
      if (it == null) continue;
      if (!it.Visible) continue;

      var typeName = it.Name;
      var displayText = $"Create {it.DisplayName}";
      var identifier = typeName.ToCamelCase().GenerateRandomIdentifier();

      var entries = new Dictionary<string, string>();

      if (attrs.Get("id") == null) entries.Add("id", identifier);
      if (attrs.Get("data-tchmi-type") == null) entries.Add("data-tchmi-type", typeName);
      if (attrs.Get("data-tchmi-width") == null) entries.Add("data-tchmi-width", "200");
      if (attrs.Get("data-tchmi-height") == null) entries.Add("data-tchmi-height", "40");

      var insertionText = string.Empty;
      foreach (var itt in entries)
        insertionText += $"{itt.Key}=\"{itt.Value}\" ";
      insertionText = " " + insertionText.Trim();

      var icon = GetIconByType(it);
      var listItem = new HtmlCompletion(displayText, insertionText, it.Description.Trim(), icon, null, ctx.Session)
      {
        SortingPriority = 500
      };

      ctrlList.Add(listItem);
    }

    return new List<HtmlCompletion>(ctrlList);
  }

  // [snip]
}

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.