commit 272976ff4cabd73d3a1829b9b4a0bea637f12d61 Author: Claire Date: Wed May 11 22:48:17 2022 +0000 Update 'Home' diff --git a/Home.md b/Home.md new file mode 100644 index 0000000..1adc13b --- /dev/null +++ b/Home.md @@ -0,0 +1,126 @@ +# 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](https://adaptivecards.io/) 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: + +```xml + + + + +``` + +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`](https://docs.microsoft.com/en-us/dotnet/api/system.windows.threading.dispatchertimer) to handle our loop. + +```csharp +/// +/// Create DispatcherTimer for handling input delay logic +/// +public DispatcherTimer keyTimer = new DispatcherTimer(); + +/// +/// timestamp in ticks (hundred nanoseconds) of last captured KeyUp event +/// +public long lastKeyUp; + +/// +/// boolean indicating whether input has changed since last tick +/// +public bool isRendered; + +/// +/// timer interval in ticks (hundred nanoseconds) +/// +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()`. + +```csharp +public MainPage() +{ + // set up a window timer for handling the keyup delay + TimerSetup(); + this.InitializeComponent(); +} + +/// +/// Sets up a window timer with a 500ms tick interval and starts the timer +/// +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. + +```csharp +/// +/// fires when txtInput sees keyboard input +/// +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; +} +/// +/// fires when txtData sees keyboard input +/// +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 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` the next keypress detected by the Monaco control. + +```csharp +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](https://github.com/hawkerm/monaco-editor-uwp) 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](https://docs.microsoft.com/en-us/uwp/api/windows.ui.xaml.uielement.keydown) instead. + +I also use [Newtonsoft.Json](https://www.newtonsoft.com/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. \ No newline at end of file