Nice Little Things in Visual Basic .NET
![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
![[community profile]](https://www.dreamwidth.org/img/silk/identity/community.png)
Being (loosely) based on the original Visual Basic, it's not surprising that Visual Basic .NET has features specifically targeted towards Windows Forms development. When I want to make a quick GUI app to run on my PC, I often find it easiest to build the main code in a C# or F# library, and to build a thin frontend layer in VB.NET, for two reasons: the incredibly aggressive (in a good way) auto-formatting that keeps me from being distracted by code style, and the nice set of quality-of-life helpers the language gives you for this exact use case.
My.Settings
The My namespace introduced in VB.NET provides a variety of shortcuts to access information about the system and application, but - unless you want to use My.Computer.Audio in a personal effort to bring back the days of goofy Microsoft Plus sound effects - the most useful member of My is probably My.Settings.
For a Windows Form project (in either C# or VB.NET), Visual Studio 2022 has a settings designer that's linked from the project properties page, under Settings > General. This allows you to define settings that your application will save and load - their names, types, scopes (user- or application-level), and their default value. A class for loading and saving these settings will get compiled into the project. Here's an example of how this works in C#:
private void button1_Click(object sender, EventArgs e) { Properties.Settings.Default.FavoriteColor = textBox1.Text; Properties.Settings.Default.Save(); }
(These settings, by the way, get stored somewhere under %LOCALAPPDATA%.)
VB.NET takes this convenience one step further: not only is the kinda-long Properties.Settings.Default replaced with My.Settings, but when you access and set them this way, settings are saved automatically before the application closes.
Private Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click My.Settings.FavoriteColor = textBox1.Text End Sub
The Handles keyword
One of my regular minor annoyances when building a WinForms interface in C# comes when I decide to delete an event handler I've written for a button, only for it to break the designer and force me to open the designer-generated C# file to delete the line of code that attached it to the button. This doesn't happen in VB.NET, because of a language feature that doesn't exist in C#: the ability to attach a function in a class to an event in that class, from within the definition of the function.
Private Sub button1_Click(sender As Object, e As EventArgs) Handles button1.Click My.Settings.FavoriteColor = textBox1.Text End Sub
The opposite issue can also occur - a deleted button leaving behind a completely unused handler. In VB.NET, these functions are made obvious by their lack of a Handles statement, so noticing them becomes easier.
Main Form and Default Instances
It's easy enough to look at the project properties page of a new WinForms project and notice that the "startup object" is set to Sub Main (the equivalent to a static Main method in C#). But the project doesn't include a Program.vb like how the C# project has a Program.cs, so where is the command to run Form1 coming from?
Well, it's defined in a file called Application.myapp:
<?xml version="1.0" encoding="utf-8"?> <MyApplicationData xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <MySubMain>true</MySubMain> <MainForm>Form1</MainForm> <SingleInstance>false</SingleInstance> <ShutdownMode>0</ShutdownMode> <EnableVisualStyles>true</EnableVisualStyles> <AuthenticationMode>0</AuthenticationMode> <SaveMySettingsOnExit>true</SaveMySettingsOnExit> </MyApplicationData>
.NET Framework projects expose these settings in the project properties window and its old UI, while .NET 6+ projects expose at least some of them in the new UI, under Application > Application Framework.
This file generates a VB file that provides the Sub Main method, which uses another odd VB.NET feature:
Protected Overrides Sub OnCreateMainForm() Me.MainForm = Form1 End Sub
Form1 is, of course, a class, but as a Windows Form, its name can also refer to a (lazy initialized) singleton instance of that class - at least, that's how I understand it. It's referred to as a "default instance" and I imagine it was added to VB.NET for this exact purpose - and to provide better backwards compatibility with Visual Basic 6.
InputBox
When I'm whipping something up quick, it's really nice to have access to the Microsoft.VisualBasic.Interaction module, particularly the InputBox method, which shows a popup window prompting the user to enter a text string. It can keep you from having to make a new form yourself.
Dim ipAddress = InputBox("Enter the IP address of the Roku:", "Add Roku") If Not String.IsNullOrEmpty(ip) Then ... End If
You can even use it from C# if you add a reference to the Microsoft.VisualBasic assembly:
string ip = Microsoft.VisualBasic.Interaction.InputBox("Enter the IP address of the Roku:", "Add Roku"); if (!string.IsNullOrEmpty(ip)) { ... }
There are other helper functions in the module too, including the delightfully named Beep().
As a quick aside - a module in VB.NET compiles to what would be a static class in C#, but it also promotes everything within the module to the scope of the namespace it's defined in. This behavior is different than modules in F#, whose members must be imported manually with an open statement. (C# also allows you to import the scope of a static class with a using, but in my experience, it's pretty rare to see this used.)
Date literals
To finish off with something that's not directly related to WinForms, but might still come in handy: VB.NET supports date and time literals. Here's an example from a WinForms prototype version of my personal thermostat app:
Private Async Sub Button11_Click(sender As Object, e As EventArgs) Handles Button11.Click TabControl1.Enabled = False Await QuickActions.SetAwayUntilTimeAsync(ThermostatClient, #7:00 AM#.TimeOfDay) Await RefreshRuntimeAsync() TabControl1.Enabled = True End Sub
The equivalent C# code I wrote used TimeSpan.FromHours(7) instead, but even though this function wants a TimeSpan, not a DateTime, I like being able to put "7:00 AM" in my code and convert from DateTime to TimeSpan for clarity. It's not just DateTime.Parse: VB.NET's date/time parsing is done at compile time, and uses a restricted (and documented) U.S. date/time format. C#'s parsing is done at runtime and uses the current locale! Good thing for user input, not great for internal constants.