![](/user_photo/_userpic.png)
- •Table of Contents
- •About the Author
- •Acknowledgments
- •Introduction
- •Version Support
- •Supported Versions
- •A Unified Platform
- •Roadmap
- •Supported Operating Systems
- •Command Line Interface
- •Desktop Development
- •Blazor
- •MAUI
- •Wrapping Up
- •.NET 6 Architecture
- •Runtimes
- •CoreCLR
- •Mono
- •WinRT
- •Managed Execution Process
- •Desktop Packs
- •Wrapping Up
- •Dotnet New
- •Dotnet Restore
- •NuGet.config
- •Dotnet Build
- •Dotnet Publish
- •Dotnet Run
- •Dotnet Test
- •Using the CLI in GitHub Actions
- •Other Commands
- •Wrapping Up
- •WinAPI
- •WinForms
- •STAThread
- •WinForms Startup
- •DPI Mode
- •Responding to Scale Events
- •Visual Styles
- •Text Rendering
- •The Message Loop
- •The Form Designer
- •WPF Startup
- •XAML Layout
- •Visual Tree
- •Data Binding
- •Windows App SDK
- •Building a Windows App SDK application
- •Using Windows APIs with Windows App SDK
- •Packaging
- •Migrating to .NET 6
- •Upgrade Assistant
- •Wrapping Up
- •Blazor WebAssembly
- •Creating a Blazor Wasm Project
- •Blazor Progressive Web Apps
- •Exploring the Blazor Client Project
- •Blazor in .NET 6
- •Blazor Component System
- •Creating Blazor Pages
- •Running a Blazor App
- •Blazor Server
- •SignalR
- •Blazor Desktop
- •Wrapping Up
- •Project Structure
- •Exploring MAUI
- •The Cross-Platform World
- •Application Lifecycle
- •MVVM
- •MVVM Toolkit
- •Wrapping Up
- •Model-View-Controller
- •Routing
- •Views
- •Controllers
- •Controller-Based APIs
- •Minimal APIs
- •Wrapping Up
- •Web Apps
- •Creating an App Service
- •Static Web Apps
- •Web App for Containers
- •Docker
- •Azure Functions
- •Deploying Azure Functions
- •Wrapping Up
- •Record Types
- •Monolith Architecture
- •Microservices
- •Container Orchestration
- •Kubernetes
- •Docker Compose
- •Dapr
- •Installing Dapr
- •Dapr State Management
- •Wrapping Up
- •Roslyn
- •Compiler API
- •Diagnostic API
- •Scripting API
- •Workspace API
- •Syntax Tree
- •Roslyn SDK
- •Source Generators
- •Writing a Source Generator
- •Debugging Source Generators
- •Wrapping Up
- •Garbage Collector
- •The Heap
- •The Stack
- •Garbage Collection
- •A Look at the Threadpool
- •Async in .NET 6
- •Await/Async
- •Cancellations
- •WaitAsync
- •Conclusion
- •Index
Chapter 4 Desktop Development
DPI mode in a WinForms application can be set in the app.config file, through an API call or via a static method that needs to be called at startup. The default template in
.NET 6 includes setting the DPI mode to System Aware through the static method, which is now the recommended way of setting the DPI mode. Depending on what version of Windows your application is running on, you can have three or four modes.
•\ |
Unaware |
•\ |
Unaware GDI Scaled |
•\ |
System Aware |
•\ |
Per Monitor |
•\ |
Per Monitor v2 |
Most of these match perfectly on the list of modes supported in the GDI+ API, but what about Per Monitor v2? This is a mode that only works on Windows 10 version 1607 and later. Per Monitor v2 extends the Per Monitor option into the non-client areas,
meaning that title bars and scroll bars, for example, will keep DPI scaling in mind as well. It also extends scaling events to child windows while Per Monitor limits this for parent windows only.
Responding to Scale Events
WinForms provides some events, helper methods, and properties to allow us to react to DPI changes and update the UI where needed.
•\ |
DpiChanged – an event that fires when the DPI is changed for the |
|
monitor the form is currently on. |
•\ |
DpiChangedAfterParent – an event that fires when the parent control |
|
or form changed DPI from code after receiving a DpiChanged event. |
•\ |
DpiChangedBeforeParent – an event that fires when the parent |
|
control or form changed DPI from code before receiving a |
|
DpiChanged event. |
•\ |
LogicalToDeviceUnits – a helper method that converts a logical |
|
size to device units, keeping the current DPI in mind, and returns a |
|
System.Drawing.Size object. |
76
|
Chapter 4 Desktop Development |
•\ |
ScaleBitmapLogicalToDevice – a helper method that scales a System. |
|
Drawing.Bitmap to device units after a DPI change. |
•\ |
DeviceDpi – a property that gets the current DPI value for the |
|
monitor the form is currently displayed on. This property comes in as |
|
a parameter on a DpiChanged event. |
Listing 4-4 shows an example of a form that uses some of these events and properties to show a form that displays DPI information. If you still have the designer window open from the first part of this chapter, you can press F7 to switch to the code behind. From there we can add an eventhandler to the DpiChanged event, shown here in the constructor. From that eventhandler, we update the text on a label that we dropped on the form using the designer.
Listing 4-4. Reacting to DPI changes
public partial class DpiForm : Form
{
public DpiForm()
{
InitializeComponent();
DpiLabel.Text = $"Current DPI: {DeviceDpi}";
DpiChanged += OnDpiChanged;
}
private void OnDpiChanged(object sender, DpiChangedEventArgs e)
{
DpiLabel.Text = $"DPI changed from {e.DeviceDpiOld} to {e.DeviceDpiNew}";
}
}
Figure 4-7 shows the result of the form when it starts up.
77
![](/html/75672/2303/html_CACnH9VZ6O.sqXh/htmlconvd-Yz4fCu88x1.jpg)
Chapter 4 Desktop Development
Figure 4-7. Form displaying the current DPI
If we set High DPI mode to DpiUnaware and change the scaling of the monitor, you’ll notice that the form seems to zoom in; it will get blurry but the text in the label will remain the same. This means that the system still calculates the size according to 96 DPI, instead of the new value. Figure 4-8 shows this result.
Figure 4-8. Result when scaling set to 175%
After setting High DPI mode to PerMonitorV2, we expect the DPI to change when we adjust scaling, and it does exactly that, but we get a new problem as shown in Figure 4-9.
Figure 4-9. Result after changing scaling
You’ll notice that the text we do see is rendered sharp; however, the label didn’t resize, so our text is being cut off. This is because the WinForms designer, by default, sets autoscale mode to Font. However, our font size is not changing; our scale factor is. We can solve this by changing the autoscale mode as shown in Listing 4-5; Figure 4-10 shows the result.
78
![](/html/75672/2303/html_CACnH9VZ6O.sqXh/htmlconvd-Yz4fCu89x1.jpg)
Chapter 4 Desktop Development
Listing 4-5. Setting autoscale mode to DPI instead of font
public DpiForm()
{
InitializeComponent();
DpiLabel.Text = $"Current DPI: {DeviceDpi}"; AutoScaleMode = AutoScaleMode.Dpi; DpiChanged += OnDpiChanged;
}
private void OnDpiChanged(object sender, DpiChangedEventArgs e)
{
DpiLabel.Text = $"DPI changed from {e.DeviceDpiOld} to {e.DeviceDpiNew}";
}
Figure 4-10. Result after changing autoscale mode
Visual Styles
Let’s continue with the startup calls; the next step is Application.EnableVisualStyles. EnableVisualStyles simply prepares all the colors, fonts, and other visual elements in the theme of the current operating system. Let’s take a form with a label, datepicker, and
a button as shown in Figure 4-11.
79
![](/html/75672/2303/html_CACnH9VZ6O.sqXh/htmlconvd-Yz4fCu90x1.jpg)
Chapter 4 Desktop Development
Figure 4-11. Form with default controls
The elements on this form automatically take the styling of Windows 10, the operating system the application was running on when taking this screenshot. We can disable loading the visual styles by replacing the call to Initialize with Listing 4-3 and commenting out Application.EnableVisualStyles method we get the result from Figure 4-12.
80