Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

BuilderHMI.Lite - Simple Drag-and-Drop WPF Layout

5.00/5 (31 votes)
18 Nov 2020CPOL7 min read 40.3K  
.NET Core WPF design as quick and intuitive as WinForms! Hear that MICROSOFT?
Add controls and specify their alignment. Left-drag to move and right-drag to size. Generate a complete .NET Core WPF Visual Studio project, build and run! Add your own MVVM bindings, event handlers, etc. to complete the app.

Background

BuilderHMI is a low-code and no-code Rapid Application Development (RAD) tool for industrial operator interface.

NO Code: UI pages can be quickly and easily designed in drag-and-drop fashion and then run directly by the app.

LOW Code: A complete WPF project with full C# source code can be generated for customization in MS Visual Studio.

Image 1

Features

  • Multiple project support with a New Project wizard
  • Ability to run standalone (Design mode and Run mode)
  • Undo/redo stack for all UI design actions
  • Unlimited UI pages with navigation
  • The full set of standard WPF controls
  • Nested Grids and Tab Controls
  • Stripchart and other specialized controls for industrial HMI
  • Motion program file editor control with download
  • Choice of styles for Button, TextBlock, Border, etc.
  • Save controls and complete pages to a Library for reuse
  • Robust communication with industrial motion controls
  • Controls send commands to motion control when clicked, etc.
  • Alarms, message logging, user login system
  • Internationalization support for foreign language users
  • Specify styling and color scheme (Skin support)
  • Browser-style Ctrl +/- zoom for UI screens
  • Live XAML window with direct edit feature
  • Customizable via external assemblies (plugins)

BuilderHMI.Lite

BuilderHMI.Lite is a subset of the full BuilderHMI app that focuses on WPF screen design and Visual Studio project generation. The repository includes source for both .NET Core and .NET Framework versions of the app.

Visual Studio Compatibility: The .NET Framework (non-Core) project is compatible with both Visual Studio 2019 and VS 2017 and uses .NET 4.6.1. The Core project can only be opened and built using VS 2019 with the ".NET Desktop Development" workload installed.

YouTube introductory video:

Image 2

Features

  • Support for the majority of the common WPF control types
  • Add controls then Left-drag to Move and Right-drag to Size
  • Left/Right/Center/Stretch horizontal alignment and Top/Bottom/Center/Stretch vertical
  • Editing functions: Cut/Copy/Paste/Delete, ToFront and ToBack
  • WPF Style support for the main window and all controls
  • Visual Studio WPF/C# project generation from a template
  • Both .NET Core and .NET Framework versions of the app
  • Both apps can generate .NET Core and .NET Framework VS projects

 

Add Your Images: I did not include example images with the project. You must copy any images required by your UI to an "Images" subfolder. For example "BuilderHMI.Lite\bin\Debug\Images".

The Controls

The BuilderHMI.Lite app includes the majority of the common WPF control types. Each of the supported control types is implemented as an “Hmi” derived class that contains additional design-time information such as initial size and what resizing is possible. The derived class is also responsible for managing the control’s property pane and generating its XAML.

Each derived class must implement the IHmiControl interface which MainWindow uses to manage the collection of controls that have been added by the user.

C#
public class HmiTextBlock : TextBlock, IHmiControl
{
    public HmiTextBlock()
    {
        Text = "TEXT BLOCK";
        SetResourceReference(StyleProperty, "TextBlockStyle");
    }

    public FrameworkElement fe { get { return this; } }
    public MainWindow OwnerPage { get; set; }
    public string NamePrefix { get { return "text"; } }
    public Size InitialSize { get { return new Size(double.NaN, double.NaN); } }
    public ECtrlFlags Flags { get { return ECtrlFlags.None; } }

    private static HmiTextBlockProperties properties = new HmiTextBlockProperties();
    public UserControl PropertyPage
        { get { properties.TheControl = this; return properties; } }

    public string ToXaml(int indentLevel, bool vs = false)
    {
        var sb = new StringBuilder();
        for (int i = 0; i < indentLevel; i++) sb.Append("    ");
        sb.Append(vs ? "<TextBlock Style=\"{DynamicResource TextBlockStyle}\"" : 
            "<HmiTextBlock");
        sb.AppendFormat(" Name=\"{0}\"", Name);
        if (!string.IsNullOrEmpty(Text)) sb.AppendFormat(" Text=\"{0}\"", 
            WebUtility.HtmlEncode(Text).Replace("\n", "&#10;"));
        OwnerPage.AppendLocationXaml(this, sb);
        sb.Append(" />");
        return sb.ToString();
    }
}

XAML Generation

The ToXaml() method in each of the “Hmi” derived classes must be capable of generating XAML for the derived class (for cut/copy/paste) as well as XAML for the base class (for VS project generation). If you copy a button on the BuilderHMI.Lite design surface and then paste it into a text editor, you will see XAML like this:

XML
<HmiButton Name="button1" Text="DELETE" ImageFile="delete.png"
 HorizontalAlignment="Left" VerticalAlignment="Top" Margin="192,344,0,0"
 Width="92" Height="72" />

However, if you generate a Visual Studio project and open it, you will see XAML like this:

XML
<Button Name="button1" Style="{DynamicResource ButtonStyle}"
 HorizontalAlignment="Left" VerticalAlignment="Top" Margin="192,344,0,0"
 Width="92" Height="72">
    <StackPanel>
        <Image Source="Images/delete.png" Stretch="None" />
        <TextBlock Text="DELETE" />
    </StackPanel>
</Button>

Only “Hmi” derived classes can be pasted to the BuilderHMI.Lite design surface. However, the standard WPF control classes are used for Visual Studio project generation.

Designer Tool Window

The Designer tool window is pretty straight forward: An Add tab with the list of available controls, an Edit tab that hosts the selected control’s property pane, and an Export tab for Visual Studio project generation. This window is tightly coupled to MainWindow and primarily just calls MainWindow methods when buttons are clicked.

Image 3

The Design Surface

The design surface is implemented by the code-behind of the MainWindow class:

  • Adding new controls and generating unique initial names
  • Control selection, Left-drag-to-move, Right-drag-to-size and Alignment changes
  • Hotkey functions Cut/Copy/Paste, Delete, ToFront, ToBack and arrow key nudge
  • XAML generation and Visual Studio project generation

Left-Drag to Move and Right-Drag to Size

I chose the left-drag-to-move and right-drag-to-size method over grab handles because I wanted an absolute minimum of screen clutter to surround the selected control. The Marker class provides the box surrounding the selected control as well as the alignment marks while moving. The Location0 class is responsible for moving or sizing a control relative to its initial control and mouse cursor locations.

Image 4

Alignment

WPF utilizes the Horizontal and Vertical Alignment, Margin, Width and Height properties to position a control within a Grid cell. The BuilderHMI.Lite app manages Margin, Width and Height to seamlessly enable the control to be moved or sized on the design surface regardless of the values of its alignment options. A control’s horizontal or vertical location can always be described by two values. However, these values are different for each alignment option:

Image 5

Styling

Styles for the supported control types are in “Styles.xaml” with a Colors section at the top of the file.

XML
<Style x:Key="TextBoxStyle" TargetType="TextBox">
    <Setter Property="Foreground" Value="{DynamicResource TextBrush}"/>
    <Setter Property="Background" Value="{DynamicResource TextBoxBackgroundBrush}"/>
    <Setter Property="BorderBrush" Value="{DynamicResource TextBrush}" />
    <Setter Property="MinWidth" Value="35" />
    <Setter Property="Padding" Value="2"/>
</Style>

All resource references are dynamic which prepares the app to be upgraded to allow skin changes on the fly.

C#
public HmiTextBox()
{
    SetResourceReference(StyleProperty, "TextBoxStyle");
}

Visual Studio Project Generation

When you first contemplate writing an app that can generate fully buildable Visual Studio WPF/C# projects, it sounds like a daunting task. The happy news is that it’s very simple. First, use VS to create a representative project, then add its various source files to your app’s project under a Template folder. The key is that these source files all have their Build Actions set to None and Copy to Output Directory set to Copy if Newer.

There are only minor differences between .NET Framework and .NET Core projects. Template files for each project type are contained in separate Template folders.

Image 6

Replace the actual project name, project namespace, etc. with tags such as “__PROJECT_NAME__”. Now all your VS project generator has to do is copy these template files to the new project folder while performing a few text replacements.

C#
// Generate AssemblyInfo.cs from the template:
string path = Path.Combine(templateFolder, "AssemblyInfo.cs");
string text = File.ReadAllText(path).Replace("__PROJECT_NAME__", projectName);
path = Path.Combine(projectFolder, "Properties");
Directory.CreateDirectory(path);
path = Path.Combine(path, "AssemblyInfo.cs");
File.WriteAllText(path, text);

The critical step for BuilderHMI.Lite, of course, is the generation of "MainWindow.xaml". Each control that the user has added must be written out in XAML format and in Z-order.

C#
// Generate xaml for all controls in Z-order:
var sortedChildren = new SortedList<int, IHmiControl>(gridCanvas.Children.Count);
foreach (object child in gridCanvas.Children)
{
    if (child is IHmiControl control)
        sortedChildren[Panel.GetZIndex(control.fe)] = control;
}
var sb = new StringBuilder();
foreach (IHmiControl child in sortedChildren.Values)
    sb.AppendLine(child.ToXaml(2, true));

Points of Interest

From a WPF developer’s perspective, I feel that these are the most interesting aspects of the project:

  • Intuitive mouse drag-and-drop layout of WPF controls in a Grid cell
  • Ability to change control alignment properties independent of location
  • XAML generation that supports both copy and paste operations and VS project generation
  • Surprisingly simple Visual Studio project generation via template files

Grid versus Canvas: I began development of drag-and-drop layout in the obvious way – by using a WPF Canvas. That’s what it’s for, right? However, as I ventured further down the rathole of implementing alignment beyond Left-Top, I realized that I was duplicating the elegant alignment system already present in a WPF Grid! I then went back to the drawing board (literally) and worked out the relationships between alignment, margin and control dimensions in order to decouple alignment from location. The result of that work is what you have here!

.NET Core: Microsoft has done an impressive job with compatibility between .NET Core and .NET Framework. Porting this app to .NET Core was practically a non-event. I simply created a new .NET Core WPF project, copied the source files over, built and ran. No source code changes were required. If this is the future of .NET, then sign me up!

Conclusion

Let’s imagine that your business regularly needs to create customized versions of a core WPF app. An example from my field would be a manufacturer of industrial machinery where each machine can be ordered with different options such as cutting heads, sensors or the number of axes of motion.

This project becomes very helpful under the above scenario. It can easily be modified to generate your business app’s core source code and additional control types can be added to the UI design framework.

  • Customized versions of the core app could then be generated much more rapidly and reliably.
  • UI screens could then be designed by people who are not experienced WPF/C# developers.

The full version of BuilderHMI extends this idea by supporting a Library of previously saved controls and entire UI pages for reuse.

History

  • Version 1, published 11/1/2020
  • Version 2, published 11/12/2020
    • .NET Core version of the app and .NET Core project generation
  • Version 3, published 11/18/2020
    • Support for additional controls
    • Clarification notes

License

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