
Whether I move forward with WPF, WinUI 3, or both, it’s clear that I need to figure out data binding if I want this app to work properly. And that’s not great: Data binding is f#$%ing difficult. So I’m trying to learn how to implement it reliably. Emphasis on the word trying.
Because this is so early on, I figured it made sense to stick with a single document version of the app first. And because I’m also trying to figure out WinUI 3–meaning, the Windows App SDK–I decided to do this work in that environment and not WPF. But I believe the basics are extremely similar, if not identical.
If you look at the official documentation, you’ll see that data binding is a way to display data in an app user interface in a way that can be automated, meaning that the value of the data is synced between the data source and a bound UI control: If the underlying data changes, the UI can change automatically. Among other things, this functionality is ideal for the document state management that .NETpad requires. It’s also worth pointing out that the separation of data and UI is good practice, whether it’s an informally structured app like mine or a more formal app design pattern like Model-View-ViewModel (MVVM).
Also, none of this is new. In Programming WPF, which was first published in 2005, Microsoft’s Chris Sells writes that data binding is the act of registering two properties with the data binding engine and letting the engine keep them synchronized, converting types as appropriate. And in WPF Unleashed, which was first published in 2006, author Adam Nathan writes that data binding is about tying together arbitrary .NET objects with a binding that you set up once and then have it do all the synchronization work for the remainder of the app’s lifetime.
It sounds so simple. And the examples one runs into online do lean heavily on the simple. For example, you might have two text boxes in a window, with the second text box being bound to the first so that whenever you edit or overwrite the text in the first text box, the text in the second one changes instantly to match. That kind of data binding is simple, of course. But even my very simple app has more complicated needs.
In .NETpad, I have a class for documents (DocumentTab previously, Document in more recent versions), for example, that has properties related to the state of an actual document–an instance of the Document class–like FileName, Contents, TextHasChanged, DocumentIsSaved, and so on. With an eye towards a future in which this app supports multiple tabs and documents, I’ve tried to manually keep the state of a document up-to-date. This isn’t too difficult with a single document. But it gets problematic when you introduce multiple tabs and documents. So I would like to automate this work as much as possible. And that’s why I’m trying to figure out data binding.
To test data binding, I’ve been creating simplifying versions of .NETpad in WinUI 3 that include just the basic app window with a TabView, a MenuBar with just a few items, a TextBox, and a Grid that I use for a basic status bar (unlike WPF, WinUI doesn’t have a StatusBar control). The TabView has an app icon in its TabStripHeader, a single TabViewItem (tab), and an empty TabStripFooter, plus the “Add new tab” button you get for free (which doesn’t do anything while I test data binding on a single document). All the control styles are in a dictionary resource, in keeping with my desire to clean that up. And because this is much easier in WinUI 3, I use a custom title bar area as per Notepad and other modernized apps.

Once that’s working, I add the Document class (Document.cs). This is a simplified version of the class, with just the FileName, Contents, TextHasChanged, and DocumentIsSaved properties. And so it starts off looking like so, simple.

And so that’s all fine. But there’s one more step: I don’t want any TabViewItems in the XAML for the main app window. Instead, there will be a CreateNewTab() method that will add a tab and an associated document, along with the various document state properties. In this test version of the app, there’s only one of each, but when this is all sorted out, I will use CreateNewTab() each time someone clicks the “Add new tab” button. For now, I will just call it from the main window constructor so I have a single tab to work with.

And that pretty much works. The tab header reads as “Untitled.txt”, which is the correct file name, but it should read as “Untitled,” because this app, like Notepad, hides the file extension. In previous .NETpad versions, I did that manually each time I wrote to the app (or, for multi-document versions, the tab header) using the Path.GetFileNameWithoutExtension() method. But in keeping with my desire for this code to be as elegant as possible, I’m going to use what’s called a custom value converter to automate that.

But first, some data binding.
As it’s currently written, .NETpad creates an instance of the Document class named “d” when the app runs. So that instance has whatever default property values I specified in the class definition: FileName is “Untitled.txt”, Contents is an empty string, and TextHasChanged and DocumentIsSaved are both false. That’s fine for a new tab. But there will be times when the user simply wants to start over with an empty document in the current tab, too. And so the Document class needs a method for that. I call that ResetDocument(), and all it does it assign default values to each property.

The hope here is that when data binding works, changing any document property–or changing all the properties using ResetDocument()–will trigger the desired changes in the app UI. For example, if we call ResetDocument(), the text box should be cleared out automatically (among other things). This is useful enough that it should be called from CreateNewTab(): Every time we create a new tab, including that first time when the app first runs, it will reset the associated document instance first.

That may not seem necessary, but this change will be useful later when I create the custom value converter: By changing the FileName (and other properties) of the Document instance as part of ResetDocument(), I can cause the display of the tab header to change too. (For now, there is no change: You run the app and it displays “Unititled.txt” in the one tab’s header, as before.)
So. Data binding.
The first concept here is that you bind a target object to a binding source (and not the reverse). If you think about the filename of the document this app will display, the binding source is the document’s FileName property (d.FileName), while the target object is the tab header (TabViewItem1.Header). If I correctly bind these two objects, the text you see in the tab header should change to match the document name when the user saves it as a new document, opens a document, or creates a new document. In short, it will simply display the name of the current document no matter the circumstances.
To make that happen, I have to modify the binding source (the FileName property in the Document class) to support binding. And that means that it must be converted from a “normal” property into what’s called a dependency property. But since I will be doing something similar with all of the properties in Document, I can take a simpler approach: I can make the entire class observable by implementing the INotifyPropertyChanged interface. As Microsoft explains in its documentation, this is used to notify binding clients that a property value has changed.
If you’ve never done any of this before, I assume your head is reeling a bit with all the terminology. I wish I could tell you that I’ve somehow mastered this myself, but that’s not the case. So … welcome to the club. Sorry.
The good news is that this really is simpler than the other approach: I just have to change the Document class so that it implements INotifyPropertyChanged like so:

The red squiggles indicate that something is missing. That something is the PropertyChanged event, which fires whenever the value of any of the properties in Document changes. Fortunately, Visual Studio knows how to add the required code for us.

Visual Studio also recommended making the Document class partial, and while I don’t fully understand why, doing so didn’t break anything, so I went with it. And here’s what the class looks like now.

So that doesn’t look too bad. But don’t worry, it’s about to get a lot more convoluted. The next step is to implement the PropertyChanged event for the FileName property specifically. This requires me to update the basic definition of the FileName property with one that supports more complex get and set accessors.
If you’re not familiar with properties and how they differ from variables, get is what happens when the property is read, while set is what happens when the property is assigned a value. Get won’t change too much despite the verbosity, but set will now run a new OnPropertyChanged() event handler when an outside object changes its value.

Of course, OnPropertyChanged() doesn’t exist, so that will have red squiggles under it. Fortunately, Visual Studio/Copilot sees the issue immediately and offers to create the event handler for you. Which I accepted.

Now it’s getting more difficult to read and I haven’t even tried to use data binding yet. This is all scaffolding, what needs to happen before you can take the next step. The good news? The app will still work normally as-is. I can test that by running the app: Untitled.txt still appears as before in the tab header. So I didn’t break anything, at least. The bad news? It’s just one property, and this is a peek at much the Document class needs to change for all this to even start working.
But first things first. Or next things next.
Let’s open an existing document.
I know that may seem like a task for another day, but I want to have some way to change the file name of the document (d.FileName). When I was just starting out with this, I created fake New, Open, and Save functions that I tied to the appropriate menus that manually changed the document class instance. But let’s just open an actual document.
Without getting into the details of opening files using WinUI 3, I basically just call a helper method called OpenDocument() from the File > Open menu item’s Click() event handler with little in the way of error checking or other housekeeping. The goal here is just to open a file and display it correctly in the app.

To be clear, this is the old-fashioned way of doing things, with no data binding. I’m manually updating the UI (and the Document object instance) as I’ve done in the past. That “works,” as expected. And it still displays the file extension, as expected.

The next steps are to eliminate the manual UI updating and use data binding instead, and to use that custom value converter to display the file name in the tab header without the extension.
So. We have a single property in the Document class, FileName, that is configured to work with data binding. The next step, obviously, is to implement that binding … somewhere in the app’s user interface. Fortunately, the “where” is clear: The tab header displays the name of the current document. It does so manually for now. But we want it to work automatically using data binding.
If you look at the CreateNewTab() method, you can see this: The Header property is assigned to d.FileName. This method needs to be built out for data binding.
First, it needs a data context, which specifies the source of the data that will be used to update the user interface. In this case, that source is our Document instance, d.
Then, it needs a new binding object instance that will specify the path within the data context (the FileName property) and the mode (one-way, in this case, but there are other options, like two-way).
Finally, we call SetBinding() to actually apply the binding, with the Header property of TabViewItem1 being bound to the binding instance (which specifies the value of the FileName property in Document).
And that all looks like so:

Note that in the original version of this method, we manually updated the tab header like so:
tabViewItem.Header = d.FileName;
But in this new version, we’re using data binding.
So that’s amazing: When I run the app after making the changes seen above, it still works in that the correct filename (Untitled.txt) appears in the tab header. That filename is only specified in one place, in ResetDocument(), and so I can change it to something else (like “Data binding works!.txt”), re-run the app, and see the results. It still works fine.

But we have to update OpenFile() too. In that helper method, I still use the old manual methods for keeping everything in sync. Two of those lines of code update the display of the tab header:

So I can simply delete those: The tab header will now be kept up-to-date automatically, so there is no need to do this manually.
Nice.
But there is one more thing.
The file name of the current document (Untitled.txt by default) displays in the tab header normally, with a file extension. And so the need for a value converter came out of the fact that I am no longer updating that tab header manually. There is no code that updates the tab header manually, and so I can’t just pass the filename through Path.GetFileNameWithoutExtension() every time it changes, as I’ve done in the past. And so I need that custom value converter I mentioned a long time ago.
Value converters, like data binding, require significant effort up front, but the goal is to only do this once so that the resulting codebase is more maintainable.
Those things are:
There are all kinds of ways to implement most of this. But the high-level goal is to consume an object of whatever kind (a string in my case), convert it to a different type (still a string, go figure, but one that lacks the file name extension), and then spit out the result. There are all kinds of reasons to use this type of thing, but this is perhaps one of the simplest. And it’s still terrible.
First, the class. This can be anywhere, I think, but I added it to MainWindow.xaml.cs, inside the namespace but outside the MainWindow class. It takes this form:

The red squiggles tell us, as always, that something is amiss. And that something is that a class that implements the IValueConverter interface has to take a certain form, with Convert and ConvertBack event handlers. Fortunately, Visual Studio offers to fix that.

Of course, the boilerplate it provides is not particularly useful: We want this to actually do something. And that something is to use the Path.GetFileNameWithoutExtension() method I keep mentioning to convert the string that is the filename into a string that is the filename minus the extension. This will not change the value of the underlying FIleName property. It’s only for the UI.
There are probably several ways to handle this. But I landed on the following.

What this does is look at the value of the property (which will be a string) and then return a empty string if it’s null or a formatted version of the string without the extension otherwise. In other words, it will return a formatted version of the string without the extension because that property will never be null. But boy does C# complain if you don’t do the checking.
After renaming the value converter class to a more memorable name (FileNameConverter), I set out to create the named instance. This could be done in C# as with all this other code, but a lot of the examples I see out there add it in XAML somewhere in the UI code. But I decided to put it in my resource dictionary instead.

Then, I had to add two lines of code to the Binding object instantiation in CreateNewTab(). (This binding remains when you open other documents–or, eventually, reset the document–so it’s not needed elsewhere.) Basically, I create a local variable that points to the FileNameConverter instance and then pass that to the Binding object as its Converter property. That looks like so:

And with that, all files opened in the app–the default untitled document and whatever documents we open later using File > Open–correctly display the underlying filename without an extension. It works!

For me, getting this working almost meant being able to reproduce it. So after this finally worked correctly, I wrote this post as I reimplemented .NETpad in WinUI 3. And now that it is working correctly, I’m of two minds. On the one hand, I’m obvious happy that something complicated finally works, everyone loves a success story. But on the other … Good God. This required a lot of work, and a lot of code, and even if you found this to be tedious, what you didn’t experience were all the times that nothing worked. This took a lot of time. And all I got done was a single property in a single class in a very of the app that supports just a single document.
That’s sobering. But every journey starts with a single step. In my case, I tried several times and faceplanted each time until I finally got to what you see here. But it’s not difficult to imagine that this work will provide a blueprint for at least some of the work to come. There are Content, TextHasChanged, and DocumentIsSaved properties to think about in this minimal version of the Document class. An AppState class to add with AutoSave and other properties of its own. Functions like New, Save, Save as, and so on. So much more to do.
But this was a step forward. A long overdue step forward. So I will at least enjoy that for a moment.
Also, I will be adding this project to GitHub so anyone can access it. And then I’ll try to keep working off that. More soon.
With technology shaping our everyday lives, how could we not dig deeper?
Thurrott Premium delivers an honest and thorough perspective about the technologies we use and rely on everyday. Discover deeper content as a Premium member.