Background
I write sophisticated CNC machine control software for milling, cutting, drilling, EDM, additive, etc. These programs run on a host PC and communicate with multi-axis motion controllers and other industrial devices. They provide touchscreen operator interface, machine control logic, network communications, diagnostics and many other functions. A colorized text editor is usually included for editing g-code.
G-code is the arcane language of CNC programming. Here is a “peck drilling cycle” example:
G83 Z1. F.001 R.03 Q.04 P0
G-code programs are generally created in one of three ways:
- Generated from a CAD model
- Generated by a specialized conversational interface
- Edited in a text editor
CAD generation is highly automated but requires the existence of a CAD model. Conversational interfaces are very application-specific and time consuming to create and maintain. Writing g-code in a text editor is error-prone and requires specialized knowledge.
I have created a block style g-code editor intended to address the shortcomings of these methods. Contact me to discuss licensing the block style g-code editor for use in your CNC applications.
Bruce’s Blocks
As part of the g-code editor development process, I created a much simpler WPF app as a testbed for new techniques such as drag-to-reorder, multi-level grouping, and synchronization between block and text views. This is that app - presented as an open source project!
The primary goal was to refine a block-style text editor with nested levels of grouping. I also wanted to create a clean and modern design aesthetic that eliminates the conventional WPF Window title bar.
Features
- Manipulate a text file as a collection of graphical blocks
- Standard editor Ctrl commands
- Multi-select block cut, copy, delete and grouping
- Font Awesome Free v5 icons
- Light and Dark themes
- Simple .NET 4.6.1 project with no dependencies
Block Manager
The BlockManager
class is responsible for managing the collection of supported block types. It is the block factory and provides bi-directional conversion between blocks and text (reading and writing both files and string
buffers). The block manager is exposed to the rest of the application via the IBlockManager
interface to enable custom versions to be injected at startup.
public interface IBlockManager
{
IBlock[] BlockTypes { get; }
IBlock[] NewBlocks(string buffer);
string ToString(IBlock[] blocks);
bool ReadFile(string path, out IBlock[] blocks);
bool WriteFile(string path, IBlock[] blocks);
}
There are IBlock
and IBlockGroup
interfaces for the block types and block group types. Each block type manages its own ListBox
items for both block view and text view, giving it the freedom to determine how best to display itself. Each block type is capable of creating a new block from a line of text.
public interface IBlock
{
IBlockGroup BlockGroup { get; set; }
ListBoxItem BlockItem { get; }
ListBoxItem CodeItem { get; }
IBlock NewBlock(string line);
}
The BlockControl
class provides the default layout for block view, including an image and three lines of text.
<Grid Height="64" Width="128">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="64" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Image Name="image" Grid.Column="0"
Source="Images/blockPalette.png" VerticalAlignment="Center" />
<StackPanel Grid.Column="1" Margin="2,0,0,0" VerticalAlignment="Center">
<TextBlock Name="tb1" Text="Palette" />
<TextBlock Name="tb2" Text="for" />
<TextBlock Name="tb3" Text="colors" />
</StackPanel>
</Grid>
Drag-to-Reorder
The DragPanel
class overrides left mouse button events to implement the drag-to-reorder functionality, and overrides MeasureOverride()
and ArrangeOverride()
to implement its layout strategy. Block types of different sizes are supported. MeasureOverride()
sets a uniform row height and column width to accommodate the largest item.
foreach (UIElement child in Children)
{
child.Measure(infSize);
itemWidth = Math.Max(itemWidth, child.DesiredSize.Width);
rowHeight = Math.Max(rowHeight, child.DesiredSize.Height);
}
Each item maintains its list order and layout position via a set of attached properties. A drop shadow effect is added to the selected item when the user starts dragging – making it appear to rise above the other items.
Multi-level Grouping
The BlockList
user control holds the block items ListBox
. It also includes a horizontal splitter and a lower region that can be populated with a second BlockList
upon demand. This arrangement enables an arbitrary number of grouping levels to be displayed. Each BlockList
holds references to its parent and child BlockList
objects in linked-list fashion.
There is quite a bit of carefully crafted bookkeeping logic behind the MainWindow
class that handles the business of implementing a block-style editor (add, delete, group, ungroup, cut, copy, paste, etc.). Nested groups are supported via plenty of recursive method calls!
private void InsertCode(IBlockGroup group, ref int index)
{
foreach (IBlock block in group.Blocks)
{
if (block is IBlockGroup)
InsertCode((IBlockGroup)block, ref index);
else
listCode.Items.Insert(index, block.CodeItem);
}
}
BlockManager
saves the collection of blocks in plain text format, both to files and to the clipboard.
private void WriteLines(StreamWriter sw, IBlock[] blocks, ref int indent)
{
foreach (IBlock block in blocks)
{
for (int i = 0; i < indent; i++) sw.Write(" ");
sw.WriteLine(block.ToString().Trim());
if (block is IBlockGroup)
{
++indent;
IBlockGroup group = (IBlockGroup)block;
WriteLines(sw, group.Blocks, ref indent);
--indent;
for (int i = 0; i < indent; i++) sw.Write(" ");
sw.WriteLine(BlockGroup.EndMarker);
}
}
}
Groups are delimited by start and end markers and group contents are indented for readability.
Custom Window Style
I wanted to implement a simple custom main window without the conventional title bar.
Here is the relevant section of my window style.
<Style x:Key="MainWindowStyle" TargetType="Window">
<Setter Property="WindowStyle" Value="None" />
<Setter Property="WindowChrome.WindowChrome">
<Setter.Value>
<WindowChrome CaptionHeight="0" GlassFrameThickness="1" ResizeBorderThickness="4" />
</Setter.Value>
</Setter>
</Style>
An invisible Button
covers the custom title bar region to support window dragging and double-click maximize/restore. The toolbar icons are Font Awesome Free (version 5). The Minimize and Close buttons execute the obvious commands. The only noteworthy trick is in the Maximize
/Restore
functionality. Setting NoResize
before maximizing prevents the maximized window from being positioned slightly off screen.
private void ToggleMaximized()
{
if (WindowState == WindowState.Maximized)
{
WindowState = WindowState.Normal;
ResizeMode = ResizeMode.CanResize;
}
else
{
if (WindowState != WindowState.Normal)
WindowState = WindowState.Normal;
ResizeMode = ResizeMode.NoResize;
WindowState = WindowState.Maximized;
}
}
Light and Dark Themes
The App
class holds the global styles and colors in its resource dictionary. App
can switch between the Light and Dark themes by simply clearing the dictionary and replacing its contents with the requested color scheme. Remember to specify dynamic resource references in your XAML.
Points of Interest
- A robust drag-to-reorder WPF
ListBox
- The multi-level block grouping scheme
- Synchronization between block and text views
- Support for the set of standard editor functions
- A simple WPF custom main window implementation
- Font Awesome Free v5 integration
- Simple Light and Dark theme support
- An example of clean, attractive and responsive WPF UI design
I hope you enjoy playing with the app and exploring the code. If you find a bug, then please let me know about it!
History
- 23rd February, 2019: Initial version
- 10th March, 2019: The GitHub repository has been updated with a new and improved
DragListBox
class featuring layout options RowMajor
, ColumnMajor
, etc. plus many other refinements.