Skip to content

Commit

Permalink
Add DarkMode and modern VisualStyles to the Visual Basic App Framework.
Browse files Browse the repository at this point in the history
  • Loading branch information
KlausLoeffelmann committed Jul 31, 2024
1 parent bd0c750 commit 0065857
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
' The .NET Foundation licenses this file to you under the MIT license.

Imports System.ComponentModel
Imports System.Diagnostics.CodeAnalysis
Imports System.Drawing
Imports System.Runtime.InteropServices
Imports System.Windows.Forms
Imports System.Windows.Forms.Analyzers.Diagnostics

Namespace Microsoft.VisualBasic.ApplicationServices

Expand All @@ -15,11 +17,21 @@ Namespace Microsoft.VisualBasic.ApplicationServices
Public Class ApplyApplicationDefaultsEventArgs
Inherits EventArgs

#Disable Warning WFO9001
#Disable Warning WFO9000
Friend Sub New(minimumSplashScreenDisplayTime As Integer,
highDpiMode As HighDpiMode)
highDpiMode As HighDpiMode,
colorMode As SystemColorMode,
visualStylesMode As VisualStylesMode)

Me.MinimumSplashScreenDisplayTime = minimumSplashScreenDisplayTime
Me.HighDpiMode = highDpiMode
Me.ColorMode = colorMode
Me.VisualStylesMode = visualStylesMode

End Sub
#Enable Warning WFO9000
#Enable Warning WFO9001

''' <summary>
''' Setting this property inside the event handler causes a new default Font for Forms and UserControls to be set.
Expand All @@ -44,5 +56,20 @@ Namespace Microsoft.VisualBasic.ApplicationServices
''' </remarks>
Public Property HighDpiMode As HighDpiMode

''' <summary>
''' Setting this property inside the event handler determines the <see cref="Application.ColorMode"/> for the application.
''' </summary>
<Experimental(DiagnosticIDs.ExperimentalDarkMode)>
Public Property ColorMode As SystemColorMode

''' <summary>
''' Setting this property inside the event handler determines the <see cref="VisualStylesMode"/> for the application.
''' </summary>
''' <returns></returns>
<Experimental(DiagnosticIDs.ExperimentalVisualStyles)>
Public Property VisualStylesMode As VisualStylesMode

End Class
#Disable Warning WFO9000
#Disable Warning WFO9001
End Namespace
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@

Imports System.Collections.ObjectModel
Imports System.ComponentModel
Imports System.Diagnostics.CodeAnalysis
Imports System.IO.Pipes
Imports System.Reflection
Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices
Imports System.Security
Imports System.Threading
Imports System.Windows.Forms
Imports System.Windows.Forms.Analyzers.Diagnostics

Imports ExUtils = Microsoft.VisualBasic.CompilerServices.ExceptionUtils
Imports VbUtils = Microsoft.VisualBasic.CompilerServices.Utils
Expand Down Expand Up @@ -138,8 +140,19 @@ Namespace Microsoft.VisualBasic.ApplicationServices
' Informs My.Settings whether to save the settings on exit or not.
Private _saveMySettingsOnExit As Boolean

' The HighDpiMode the user picked from the AppDesigner or assigned to the ApplyHighDpiMode's Event.
' The HighDpiMode the user picked from the AppDesigner or assigned to the ApplyApplicationsDefault event.
Private _highDpiMode As HighDpiMode = HighDpiMode.SystemAware
#Disable Warning WFO9001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
' The ColorMode (Classic/Light, System, Dark) the user assigned to the ApplyApplicationsDefault event.
' Note: We aim to expose this to the App Designer in later runtime/VS versions.
Private _colorMode As SystemColorMode = SystemColorMode.Classic
#Enable Warning WFO9001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.

#Disable Warning WFO9000 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
' The VisualStylesMode (Default is Classic) the user assigned to the ApplyApplicationsDefault event.
' Note: We aim to expose this to the App Designer in later runtime/VS versions.
Private _visualStylesMode As VisualStylesMode = VisualStylesMode.Classic
#Enable Warning WFO9000 ' Type is for evaluation purposes only and is subject to change or removal in future updates.

''' <summary>
''' Occurs when the network availability changes.
Expand Down Expand Up @@ -249,15 +262,15 @@ Namespace Microsoft.VisualBasic.ApplicationServices
RaiseEvent(sender As Object, e As UnhandledExceptionEventArgs)
If _unhandledExceptionHandlers IsNot Nothing Then

' In the case that we throw from the <see cref="UnhandledException"/> handler, we don't want to
' run the <see cref="UnhandledException"/> handler again.
' In the case that we throw from the UnhandledException handler, we don't want to
' run the UnhandledException handler again.
_processingUnhandledExceptionEvent = True

For Each handler As UnhandledExceptionEventHandler In _unhandledExceptionHandlers
handler?.Invoke(sender, e)
Next

' Now that we are out of the <see cref="UnhandledException"/> handler, treat exceptions normally again.
' Now that we are out of the UnhandledException handler, treat exceptions normally again.
_processingUnhandledExceptionEvent = False
End If
End RaiseEvent
Expand Down Expand Up @@ -291,8 +304,8 @@ Namespace Microsoft.VisualBasic.ApplicationServices
If authenticationMode = AuthenticationMode.Windows Then
Try
' Consider: Sadly, a call to: System.Security.SecurityManager.IsGranted(New SecurityPermission(SecurityPermissionFlag.ControlPrincipal))
' Will only check THIS caller so you'll always get TRUE.
' What is needed is a way to get to the value of this on a demand basis.
' Will only check the THIS caller so you'll always get TRUE.
' What we need is a way to get to the value of this on a demand basis.
' So I try/catch instead for now but would rather be able to IF my way around this block.
Thread.CurrentPrincipal = New Principal.WindowsPrincipal(Principal.WindowsIdentity.GetCurrent)
Catch ex As SecurityException
Expand Down Expand Up @@ -349,7 +362,11 @@ Namespace Microsoft.VisualBasic.ApplicationServices
Dim tokenSource As New CancellationTokenSource()
tokenSource.CancelAfter(SECOND_INSTANCE_TIMEOUT)
Try
Dim awaitable As ConfiguredTaskAwaitable = SendSecondInstanceArgsAsync(applicationInstanceID, commandLine, cancellationToken:=tokenSource.Token).ConfigureAwait(False)
Dim awaitable As ConfiguredTaskAwaitable = SendSecondInstanceArgsAsync(
pipeName:=applicationInstanceID,
args:=commandLine,
cancellationToken:=tokenSource.Token).ConfigureAwait(False)

awaitable.GetAwaiter().GetResult()
Catch ex As Exception
Throw New CantStartSingleInstanceException()
Expand Down Expand Up @@ -492,20 +509,27 @@ Namespace Microsoft.VisualBasic.ApplicationServices
' in a derived class and setting `MyBase.MinimumSplashScreenDisplayTime` there.
' We are picking this (probably) changed value up, and pass it to the ApplyDefaultsEvents
' where it could be modified (again). So event wins over Override over default value (2 seconds).
' b) We feed the default HighDpiMode (SystemAware) to the EventArgs. With the introduction of
' the HighDpiMode property, we give Project System the chance to reflect the HighDpiMode
' in the App Designer UI and have it code-generated based on a modified Application.myapp, which
' would result it to be set in the derived constructor. (See the hidden file in the Solution Explorer
' "My Project\Application.myapp\Application.Designer.vb for how those UI-set values get applied.)
' b) We feed the defaults for HighDpiMode, ColorMode, VisualStylesMode to the EventArgs.
' With the introduction of the HighDpiMode property, we changed Project System the chance to reflect
' those default values in the App Designer UI and have it code-generated based on a modified
' Application.myapp, which would result it to be set in the derived constructor.
' (See the hidden file in the Solution Explorer "My Project\Application.myapp\Application.Designer.vb
' for how those UI-set values get applied.)
' Once all this is done, we give the User another chance to change the value by code through
' the ApplyDefaults event.
' Overriding MinimumSplashScreenDisplayTime needs still to keep working!
' Note: Overriding MinimumSplashScreenDisplayTime needs still to keep working!
#Disable Warning WFO9001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
#Disable Warning WFO9000 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
Dim applicationDefaultsEventArgs As New ApplyApplicationDefaultsEventArgs(
MinimumSplashScreenDisplayTime,
HighDpiMode) With
HighDpiMode,
ColorMode,
VisualStylesMode) With
{
.MinimumSplashScreenDisplayTime = MinimumSplashScreenDisplayTime
}
#Enable Warning WFO9000 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
#Enable Warning WFO9001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.

RaiseEvent ApplyApplicationDefaults(Me, applicationDefaultsEventArgs)

Expand All @@ -521,17 +545,26 @@ Namespace Microsoft.VisualBasic.ApplicationServices

_highDpiMode = applicationDefaultsEventArgs.HighDpiMode

#Disable Warning WFO9001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
#Disable Warning WFO9000 ' Type is for evaluation purposes only and is subject to change or removal in future updates.

_colorMode = applicationDefaultsEventArgs.ColorMode
_visualStylesMode = applicationDefaultsEventArgs.VisualStylesMode

' Then, it's applying what we got back as HighDpiMode.
Dim dpiSetResult As Boolean = Application.SetHighDpiMode(_highDpiMode)

If dpiSetResult Then
_highDpiMode = Application.HighDpiMode
End If
Debug.Assert(dpiSetResult, "We could net set the HighDpiMode.")

' And finally we take care of EnableVisualStyles.
If _enableVisualStyles Then
Application.EnableVisualStyles()
End If
' Now, let's set VisualStyles and ColorMode:
Application.SetColorMode(_colorMode)
Application.SetDefaultVisualStylesMode(_visualStylesMode)

#Enable Warning WFO9000 ' Type is for evaluation purposes only and is subject to change or removal in future updates.
#Enable Warning WFO9001 ' Type is for evaluation purposes only and is subject to change or removal in future updates.

' We'll handle "/nosplash" for you.
If Not (commandLineArgs.Contains("/nosplash") OrElse Me.CommandLineArgs.Contains("-nosplash")) Then
Expand Down Expand Up @@ -559,7 +592,7 @@ Namespace Microsoft.VisualBasic.ApplicationServices
' It is important not to create the network object until the ExecutionContext has everything on it.
' By now the principal will be on the thread so we can create the network object.
' The timing is important because the network object has an AsyncOperationsManager in it that marshals
' the network changed event to the main thread. The asycnOperationsManager does a CreateOperation()
' the network changed event to the main thread. The asyncOperationsManager does a CreateOperation()
' which makes a copy of the executionContext. That execution context shows up on your thread during
' the callback so I delay creating the network object (and consequently the capturing of the execution context)
' until the principal has been set on the thread. This avoids the problem where My.User isn't set
Expand Down Expand Up @@ -796,6 +829,32 @@ Namespace Microsoft.VisualBasic.ApplicationServices
End Set
End Property

''' <summary>
''' Gets or sets the VisualStylesMode for the Application.
''' </summary>
<Experimental(DiagnosticIDs.ExperimentalVisualStyles)>
Protected Property VisualStylesMode As VisualStylesMode
Get
Return _visualStylesMode
End Get
Set(value As VisualStylesMode)
_visualStylesMode = value
End Set
End Property

''' <summary>
''' Gets or sets the ColorMode for the Application.
''' </summary>
<Experimental(DiagnosticIDs.ExperimentalDarkMode)>
Protected Property ColorMode As SystemColorMode
Get
Return _colorMode
End Get
Set(value As SystemColorMode)
_colorMode = value
End Set
End Property

<EditorBrowsable(EditorBrowsableState.Advanced)>
Protected Property IsSingleInstance() As Boolean
Get
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Imports Microsoft.VisualBasic.CompilerServices

Imports ExUtils = Microsoft.VisualBasic.CompilerServices.ExceptionUtils
Imports VbUtils = Microsoft.VisualBasic.CompilerServices.Utils
Imports NativeMethods = Microsoft.VisualBasic.CompilerServices.NativeMethods

Namespace Microsoft.VisualBasic

Expand Down
8 changes: 8 additions & 0 deletions src/Microsoft.VisualBasic.Forms/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Microsoft.VisualBasic.ApplicationServices.ApplyApplicationDefaultsEventArgs.ColorMode() -> System.Windows.Forms.SystemColorMode
Microsoft.VisualBasic.ApplicationServices.ApplyApplicationDefaultsEventArgs.ColorMode(AutoPropertyValue As System.Windows.Forms.SystemColorMode) -> Void
Microsoft.VisualBasic.ApplicationServices.ApplyApplicationDefaultsEventArgs.VisualStylesMode() -> System.Windows.Forms.VisualStylesMode
Microsoft.VisualBasic.ApplicationServices.ApplyApplicationDefaultsEventArgs.VisualStylesMode(AutoPropertyValue As System.Windows.Forms.VisualStylesMode) -> Void
Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.ColorMode() -> System.Windows.Forms.SystemColorMode
Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.ColorMode(value As System.Windows.Forms.SystemColorMode) -> Void
Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.VisualStylesMode() -> System.Windows.Forms.VisualStylesMode
Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase.VisualStylesMode(value As System.Windows.Forms.VisualStylesMode) -> Void
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

[assembly: System.Runtime.InteropServices.ComVisible(false)]

[assembly: InternalsVisibleTo("Microsoft.VisualBasic.Forms, PublicKey=002400000480000094000000060200000024000052534131000400000100010007d1fa57c4aed9f0a32e84aa0faefd0de9e8fd6aec8f87fb03766c834c99921eb23be79ad9d5dcc1dd9ad236132102900b723cf980957fc4e177108fc607774f29e8320e92ea05ece4e821c0a5efe8f1645c4c0c93c1ab99285d622caa652c1dfad63d745d6f2de5f17e5eaf0fc4963d261c8a12436518206dc093344d5ad293")]

[assembly: InternalsVisibleTo("System.Windows.Forms, PublicKey=00000000000000000400000000000000")]
[assembly: InternalsVisibleTo("System.Windows.Forms.Design, PublicKey=00000000000000000400000000000000")]
[assembly: InternalsVisibleTo("System.Windows.Forms.Design.Editors, PublicKey=00000000000000000400000000000000")]
Expand Down

0 comments on commit 0065857

Please sign in to comment.