4 Home
Claire edited this page 2 years ago

Code Notebook

About the app

For years now, I've been working on an application idea, which amounts to using well-formatted JSON input to dynamically generate an application UI that is easy to navigate, regardless of the amount of data used to generate the app's interface. I wanted to enable users to include their own formatting instructions along with the data file, so the app can show information in an accessible format, rather than just rendering unformatted key-value pairs.

I was working at Microsoft in a dev-related org when I started working on this project, and after seeing Adaptive Cards in action elsewhere, I decided I wanted to implement that standard as a means of formatting data files. To make it easier to develop my application, I wanted a desktop app that let me work on card payloads and data models, with realtime rendering. By "realtime rendering," I'm referring to automatic rendering of content as the user inputs data, rather than requiring the user to click a button or use a keyboard shortcut to render the output.

Rendering output in realtime

To automatically render output as the user inputs text in a given textarea or textbox, we first need to listen for keyboard events on the relevant controls. Because I don't want to render until the user has stopped inputting text, I'm using the UWP KeyDown event, which fires every time the user presses a key. That's easy enough to do with my Monaco controls:

<Border Grid.Row="1" Grid.RowSpan="3" Grid.Column="1" BorderBrush="#777" BorderThickness="1" Margin="20 10">
    <monaco:CodeEditor x:Name="txtData"
        TabIndex="0"
        HasGlyphMargin="False"
        CodeLanguage="json"                           
        KeyDown="txtData_KeyDown">
    </monaco:CodeEditor>
</Border>

Rerendering after every keypress is costly in terms of performance. Using a timer to delay the app's response to text input makes it possible to selectively render, once the user has stopped typing. This is also the fundamental basis of any modern video game - a loop that's always running, which executes different code depending on the most recent input.

We're going to use System.Threading.Tasks.DispatcherTimer to handle our loop.

/// <summary>
/// Create DispatcherTimer for handling input delay logic
/// </summary>
public DispatcherTimer keyTimer = new DispatcherTimer();

/// <summary>
/// timestamp in ticks (hundred nanoseconds) of last captured KeyUp event
/// </summary>
public long lastKeyUp;

/// <summary>
/// boolean indicating whether input has changed since last tick
/// </summary>
public bool isRendered;

/// <summary>
/// timer interval in ticks (hundred nanoseconds)
/// </summary>
public long interval = 5000000;

The above variables are pretty self-explanatory. interval is set to half a second, which is enough to accommodate even rapid typists (I type around 120-130 WPM).

The timer needs to be instantiated when the main application window loads, so we need to call that setup function from MainPage().

public MainPage()
{
    // set up a window timer for handling the keyup delay
    TimerSetup();
    this.InitializeComponent();
}

/// <summary>
/// Sets up a window timer with a 500ms tick interval and starts the timer
/// </summary>
public void TimerSetup()
{
    // on every timer tick, run TimerTick()
    keyTimer.Tick += TimerTick;
    // set the timer interval to half a second
    keyTimer.Interval = TimeSpan.Parse("00:00:00.05");
    // start the timer
    keyTimer.Start();
}

Upon starting the timer, TimerTick() will run every half second, checking to see if the application is ready to render user input. This is where the isRendered Boolean is used: any time the user presses a key, isRendered is set to false, and lastKeyUp is updated to the current timer value, which is an integer representing the number of ticks since the timer started.

/// <summary>
/// fires when txtInput sees keyboard input
/// </summary>
private void txtInput_KeyDown(CodeEditor sender, WebKeyEventArgs e)
{
    // last key up event = integer value of current time
    lastKeyUp = DateTime.Now.Ticks;
    // user is inputting text, so we're going to rerender
    isRendered = false;
}

/// <summary>
/// fires when txtData sees keyboard input
/// </summary>
private void txtData_KeyDown(CodeEditor sender, WebKeyEventArgs args)
{
    // last key up event = integer value of current time
    lastKeyUp = DateTime.Now.Ticks;
    // user is inputting text, so we're going to rerender
    isRendered = false;
}

The timer fires TimerTick() every half second. The first thing this function does is check if isRendered is true, and if so, ends the function. Since we don't want the renderer to do anything if less than half a second has passed since the last KeyDown event, the function next checks to make sure at least half a second has passed since the value of lastKeyUp. Finally, if the function renders output, isRendered is set to true, and will be reset to false on the next keypress detected by the Monaco control.

void TimerTick(object sender, object args)
{
    // if isRendered is true, there have been no changes to input since the last tick
    if (isRendered)
    {
        return;
    }
    // otherwise, we done got input, so do the thing
    else
    {
        if (DateTime.Now.Ticks >= lastKeyUp + interval )
        {

            // render output
            ...

            isRendered = true;
        }
        else
        {
            return;
        }
    }
}

Libraries used

I try to avoid using third-party libraries whenever possible. However, for this particular project, I wanted to make use of the Monaco text editor, since it supports things like code folding and syntax highlighting, which are useful features when working with large JSON files. I ended up using this unofficial Monaco wrapper and have been happy with it. My project uses its own copy of this library, to avoid potentially breaking updates to that project. If you don't want to use the Monaco wrapper, you can still use my project's code; just use Windows.UI.Xaml.UIElement.KeyDown instead.

I also use Newtonsoft.Json, also known as Json.NET. This library is very actively maintained and developed, and is much more efficient at working with JSON than Microsoft's native .NET implementation.