Tried It Out

With the combination of Visual Studio 2019 + .NET Core 3.0, WPF is usable, so I created a new project and tried it out.

For now, I’ll try writing as if I’m using the traditional WPF, forgetting that I’m using .NET Core, and see how it feels.

Usage methods are introduced by various people, but you can use it by installing Visual Studio 2019 and .NET Core 3.0, and then checking ON “Use preview .NET Core SDK” in Visual Studio options.

I think this check will become unnecessary once .NET Core 3.0 becomes the official version.

C# 8.0 Nullable Reference Types

At the same time, I also tried the nullable reference types introduced in C# 8.0.

This time, since I’m trying it out by creating a new project, I’ve enabled nullable reference types for the entire project by describing it in the .csproj as follows.

<propertygroup>
    <outputtype>WinExe</outputtype>
    <targetframework>netcoreapp3.0</targetframework>
    <usewpf>true</usewpf>
    <langversion>8.0</langversion>
    <nullablecontextoptions>enable</nullablecontextoptions>
</propertygroup>

With this setting, you can no longer put null in the traditional way of writing reference types. Also, it will now issue a warning when accessing a variable that might be null.

What I Made

Since I’m creating a new project, I decided to set a theme.
Because I often see node-based creation environments in tools like Unreal Engine (UE4), which I use at work, and Houdini, which I’ve been paying attention to recently, I chose “Node-Based UI” as the theme.

Just connecting nodes isn’t interesting, so I’ve included calculator functionality.

What I’ve made, as of writing this article, looks like this:

Currently, you can connect nodes to perform addition and subtraction.

The complete project is available on GitHub.

  • Note: This node-based calculator is still under development, so it is frequently modified.

References from NuGet

This time, I’ve added references to the following four from NuGet:

  • Extended.Wpf.Toolkit
  • gong-wpf-dragdrop
  • Microsoft.Xaml.Behaviors.Wpf
  • ReactiveProperty

Among these, Extended.Wpf.Toolkit and Microsoft.Xaml.Behaviors.Wpf don’t seem to support .NET Core 3.0 yet, resulting in the following warnings:

NU1701: Package 'Extended.Wpf.Toolkit 3.5.0' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.
NU1701: Package 'Microsoft.Xaml.Behaviors.Wpf 1.0.1' was restored using '.NETFramework,Version=v4.6.1' instead of the project target framework '.NETCoreApp,Version=v3.0'. This package may not be fully compatible with your project.

For now, there are no issues with the functionality I’m using, and since .NET Core 3.0 is still a preview version, I’ve decided not to worry about it.

If these warnings persist even after .NET Core 3.0 becomes the official version, actions such as handling it myself and building might be necessary.

A Little Introduction to the Structure

I will focus on introducing the functionality for adding new nodes among what I created this time. (I thought about writing about drawing lines and performing calculations too, but since it’s actively under development, there’s a high possibility of future changes.)

Node Placement

The XAML for the View where nodes are placed is as follows.

<itemscontrol itemsource="{Binding Nodes}" 
        background="Transparent"
        dd:DragDrop.IsDropTarget="True" 
        dd:DragDrop.DropHandler="{Binding}"
        >
    <itemscontrol.itemspanel>
        <itemspaneltemplate>
            <canvas />
        </itemspaneltemplate>
    </itemscontrol.itemspanel>
    <itemscontrol.itemtemplate>
        <datatemplate>
            <node:NodeContainer />
        </datatemplate>
    </itemscontrol.itemtemplate>
    <itemscontrol.itemcontainerstyle>
        <style targettype="ContentPresenter">
            <setter property="Canvas.Top" value="{Binding PositionY.Value}"/>
            <setter property="Canvas.Left" value="{Binding PositionX.Value}"/>
        </style>
    </itemscontrol.itemcontainerstyle>
</itemscontrol>

By specifying Canvas for ItemsPanel, placement using absolute coordinates is enabled.

<itemscontrol.itemspanel>
    <itemspaneltemplate>
        <canvas />
    </itemspaneltemplate>
</itemscontrol.itemspanel>

Node position setting is handled by binding Canvas.Top and Left to the properties of the node’s ViewModel in ItemContainerStyle.

<itemscontrol.itemcontainerstyle>
    <style targettype="ContentPresenter">
        <setter property="Canvas.Top" value="{Binding PositionY.Value}"/>
        <setter property="Canvas.Left" value="{Binding PositionX.Value}"/>
    </style>
</itemscontrol.itemcontainerstyle>

Also, since node generation via drag-and-drop is incorporated into this ItemsControl, dd:DragDrop.IsDropTarget and dd:DragDrop.DropHandler are specified.

This is a feature of gong-wpf-dragdrop added via NuGet; writing it this way allows you to write the drop handling logic in the ViewModel bound to the ItemsControl.

<itemscontrol itemsource="{Binding Nodes}" 
        background="Transparent"
        dd:DragDrop.IsDropTarget="True" 
        dd:DragDrop.DropHandler="{Binding}"
        >

New Node Generation

The following source code is the process when dropping onto the View where nodes are placed.

dropInfo.Data contains the DataContext (bound ViewModel) of the dropped View, so the process changes depending on its type.

public void Drop(IDropInfo dropInfo)
{
    switch (dropInfo.Data)
    {
        case NodeViewModel node:
            {
                nowDragItem = null;
            }
            break;

        case NodeConnectionViewModel connection:
            {
                connection.LineToX.Value = 0;
                connection.LineToY.Value = 0;
                connection.Visible.Value = Visibility.Hidden;
            }
            break;
        case ToolBoxItemViewModel item:
            {
                var newItem = Activator.CreateInstance(item.NodeModelType) as NodeBase;
                if (newItem != null)
                {
                    newItem.PositionX = dropInfo.DropPosition.X;
                    newItem.PositionY = dropInfo.DropPosition.Y;
                    mainModel.Nodes.Add(newItem);
                }                
            }
            break;
        default:
            nowDragItem = null;
            break;
    }
}

The Drop process when creating a new node is the part that handles ToolBoxItemViewModel.

The dropped ViewModel has a property called NodeModelType of type Type, and based on this, an instance of the node is generated.

The generated node is given coordinates based on the drop position and added to the model’s Nodes array.

This addition to the array propagates from Model -> ViewModel -> View, and the new node is displayed in the ItemsControl explained earlier.

Also, where Activator.CreateInstance is used, I initially skipped the null check out of laziness, but since the nullable reference types feature was ON, a warning appeared.

Adding the null check made the warning disappear, apparently because the compiler performs flow analysis. Amazing!

case ToolBoxItemViewModel item:
    {
        var newItem = Activator.CreateInstance(item.NodeModelType) as NodeBase;
        if (newItem != null) // Null check added
        {
            newItem.PositionX = dropInfo.DropPosition.X;
            newItem.PositionY = dropInfo.DropPosition.Y;
            mainModel.Nodes.Add(newItem);
        }                    
    }
    break;

Summary

While writing this article, I intended to summarize any points like “you should be careful about this when using WPF with .NET Core,” but… there weren’t any in particular.

You can use it just like when using the previous .NET Framework version of WPF.

It was said that the inability to use the XAML designer in the .NET Core version was a disadvantage, but it seems it became usable in VS2019 Preview 3 (haven’t tried it yet), so I think this will also cease to be an issue eventually.

Also, regarding nullable reference types, I initially thought the warnings might be intrusive, but that wasn’t the case. Instead, I felt it was a helpful feature because it points out places where I might have overlooked potential null access.

Introducing it into existing projects might result in a flood of warnings and be difficult, but I think enabling it for newly created projects enhances code safety.

I believe .NET Core will become mainstream for future C# development, but I felt that developing desktop applications using WPF is also perfectly fine.