diff --git a/.github/images/demo-winforms.png b/.github/images/demo-winforms.png new file mode 100644 index 0000000..c3ba765 Binary files /dev/null and b/.github/images/demo-winforms.png differ diff --git a/.github/images/demo-wpf.png b/.github/images/demo-wpf.png new file mode 100644 index 0000000..dd8f430 Binary files /dev/null and b/.github/images/demo-wpf.png differ diff --git a/.github/workflows/dotnetpackage.yml b/.github/workflows/dotnetpackage.yml new file mode 100644 index 0000000..8ffcc2d --- /dev/null +++ b/.github/workflows/dotnetpackage.yml @@ -0,0 +1,45 @@ +name: .NET + +on: + push: + branches: [ master ] + + workflow_dispatch: + +jobs: + build: + env: + ProjectName: DarkNet + + runs-on: windows-latest + + steps: + - name: Clone repository + uses: actions/checkout@v3 + + - name: Restore dependencies + run: dotnet restore + + - name: Build library + run: dotnet build ${{ env.ProjectName }} --no-restore --configuration Release + + - name: Pack library + run: dotnet pack ${{ env.ProjectName }} --no-build --configuration Release + + - name: Build demos + run: | + dotnet build darknet-demo-winforms --no-restore --configuration Release + dotnet publish darknet-demo-wpf --configuration Release -p:PublishSingleFile=true --runtime win-x86 --self-contained false + + - name: Flatten artifacts + run: | + mkdir upload + copy ${{ env.ProjectName }}/bin/Release/*.*nupkg upload + copy darknet-demo-winforms/bin/Release/*.exe upload + copy darknet-demo-wpf/bin/Release/**/publish/*.exe upload + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: ${{ env.ProjectName }} + path: upload diff --git a/.gitignore b/.gitignore index 27b5c94..1aeaa35 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ _ReSharper*/ .vs/ #Nuget packages folder packages/ +/upload/ diff --git a/Readme-Forms.md b/Readme-Forms.md deleted file mode 100644 index 9390f1b..0000000 --- a/Readme-Forms.md +++ /dev/null @@ -1,124 +0,0 @@ -This is a .NET library that you can use to enable Windows 10's dark mode for your application's title bars and system context menus, similar to the dark title bar in Command Prompt. - -[![Windows Forms window with dark title bar](https://i.imgur.com/3PDwxTyl.png)](https://i.imgur.com/3PDwxTy.png) - -- [Requirements](#requirements) -- [Installation](#installation) - - [WPF](#wpf) - - [Windows Forms](#windows-forms) -- [Usage](#usage) - - [On application startup](#on-application-startup) - - [Before showing a new window](#before-showing-a-new-window) - - [Complete example](#complete-example) -- [Demo](#demo) -- [Limitations](#limitations) -- [Acknowledgements](#acknowledgements) - -## Requirements - -- .NET Framework 4.7.2 or later, or .NET Core 3.1 or later -- Windows 10 version 1809 (October 2018 Update) or later for dark mode to work - - You can run your app on earlier Windows versions, but the title bar won't turn dark -- WPF or Windows Forms - -## Installation - -There are two packages available, one for WPF and one for Windows Forms. You can install the one that corresponds to the GUI technology that your program uses. - -### WPF -```ps1 -dotnet add package DarkNet-WPF -``` -[See **DarkNet-WPF** on NuGet](https://www.nuget.org/packages/DarkNet-WPF/) - -### Windows Forms -```ps1 -dotnet add package DarkNet-Forms -``` -[See **DarkNet-Forms** on NuGet](https://www.nuget.org/packages/DarkNet-Forms/) - -## Usage - -You must do both of the following steps. - -### On application startup - -Before showing **any** windows in your application, you must call -```cs -darknet.forms.DarkNet.SetDarkModeAllowedForProcess(true); -``` - -A good place to call this is at the top of the `Main()` method of your application. - -```cs -internal static class Program { - - [STAThread] - private static void Main() { - DarkNet.SetDarkModeAllowedForProcess(true); - - // Incomplete example; see below for complete example including showing your first window. - } -} -``` - -### Before showing a new window - -Before showing each window in your application, you have to enable dark mode for that window. - -```cs -darknet.forms.DarkNet.SetDarkModeAllowedForWindow(window, true); -``` - -You must do this before showing the window with `Show()` or `Application.Run()`. If you call it too late (such as after the window is shown), the DLL calls will have no effect on Windows. - -Call the aforementioned DarkNet method after the window is constructed. - -```cs -Form mainForm = new Form1(); -DarkNet.SetDarkModeAllowedForWindow(mainForm, true); -``` - -You must also perform this step for all subsequent windows you show in your application, not just the first window. - -### Complete example - -```cs -internal static class Program { - - [STAThread] - private static void Main() { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - DarkNet.SetDarkModeAllowedForProcess(true); - - Form mainForm = new Form1(); - DarkNet.SetDarkModeAllowedForWindow(mainForm, true); - mainForm.Show(); - - Application.Run(mainForm); - } - -} -``` - -## Demo - -Download and run `darknet-demo-forms.exe` from the [latest release](https://github.com/Aldaviva/DarkNet/releases). - -[![Windows Forms window with dark title bar](https://i.imgur.com/3PDwxTyl.png)](https://i.imgur.com/3PDwxTy.png) - -You can also clone this repository and build the `darknet-demo-forms` project yourself using Visual Studio Community 2019. - -## Limitations -- This library currently requires a fairly recent version of Windows and the .NET runtime. This could be relaxed in a future version to allow inclusion in applications that run in a wider range of environments. -- This library currently does not expose whether the active Windows app mode is set to Dark or Light. This may be possible to add in a future version to allow you to implement a "follow Windows app mode" strategy in your application. -- This library only changes the theme of the title bar/window chrome/non-client area, as well as the system context menu (the menu that appears when you right click on the title bar, or left click on the title bar icon, or hit `Alt`+`Space`). It does not change the theme of the client area of your window. It is up to you to make that look different when dark mode is enabled. -- This library currently does not help you persist a user choice for the mode they want your application to use. You can expose an option and persist that yourself, then pass the desired value to the methods in this library (*e.g.* call `DarkNet.SetDarkModeAllowedForProcess(false)` for light mode, or just don't call it at all). - -## Acknowledgements - -- [Milan Burda](https://github.com/miniak) for explaining how to add this in Electron ([electron/electron #23479: [Windows] Title bar does not respect dark mode](https://github.com/electron/electron/issues/23479)) -- [dyasta](https://stackoverflow.com/users/191514/dyasta) for [an explanation on Stack Overflow](https://stackoverflow.com/a/58547831/979493) -- [Richard Yu](https://github.com/ysc3839) for [implementing this in a C++ library](https://github.com/ysc3839/win32-darkmode) -- [Berrysoft](https://github.com/Berrysoft) for [implementing this in Mintty, the Cygwin Terminal emulator](https://github.com/mintty/mintty/issues/983) ([further discussion](https://github.com/mintty/mintty/pull/984)) \ No newline at end of file diff --git a/Readme-WPF.md b/Readme-WPF.md deleted file mode 100644 index 7f375ac..0000000 --- a/Readme-WPF.md +++ /dev/null @@ -1,137 +0,0 @@ -This is a .NET library that you can use to enable Windows 10's dark mode for your application's title bars and system context menus, similar to the dark title bar in Command Prompt. - -[![WPF window with dark title bar](https://i.imgur.com/yWWyS0Jl.png)](https://i.imgur.com/yWWyS0J.png) - -- [Requirements](#requirements) -- [Installation](#installation) - - [WPF](#wpf) - - [Windows Forms](#windows-forms) -- [Usage](#usage) - - [On application startup](#on-application-startup) - - [Before showing a new window](#before-showing-a-new-window) - - [Complete example](#complete-example) -- [Demo](#demo) -- [Limitations](#limitations) -- [Acknowledgements](#acknowledgements) - -## Requirements - -- .NET Framework 4.7.2 or later, or .NET Core 3.1 or later -- Windows 10 version 1809 (October 2018 Update) or later for dark mode to work - - You can run your app on earlier Windows versions, but the title bar won't turn dark -- WPF or Windows Forms - -## Installation - -There are two packages available, one for WPF and one for Windows Forms. You can install the one that corresponds to the GUI technology that your program uses. - -### WPF -```ps1 -dotnet add package DarkNet-WPF -``` -[See **DarkNet-WPF** on NuGet](https://www.nuget.org/packages/DarkNet-WPF/) - -### Windows Forms -```ps1 -dotnet add package DarkNet-Forms -``` -[See **DarkNet-Forms** on NuGet](https://www.nuget.org/packages/DarkNet-Forms/) - -## Usage - -You must do both of the following steps. - -### On application startup - -Before showing **any** windows in your application, you must call -```cs -darknet.wpf.DarkNet.SetDarkModeAllowedForProcess(true); -``` - -A good place to call this is in an event handler for your Application's `Startup` event. - -By default, WPF apps have an `App.xaml` file that specifies the first `Window` to show in the `StartupUri` attribute: -```xaml - -``` - -This does not allow you to run custom code before showing the window, so remove `StartupUri` and add `Startup`, which specifies an event handler method. -```xaml - -``` - -In `App.xaml.cs`, create a method to handle this event and call the aforementioned DarkNet method. -```cs -public partial class App: Application { - - private void App_OnStartup(object sender, StartupEventArgs e) { - DarkNet.SetDarkModeAllowedForProcess(true); - - // Incomplete example; see below for complete example including showing your first window. - } -} -``` - -### Before showing a new window - -Before showing each window in your application, you have to enable dark mode for that window. - -```cs -darknet.wpf.DarkNet.SetDarkModeAllowedForWindow(window, true); -``` - -You must do this when the `SourceInitialized` event is fired for the window you are about to show. If you do it before this (such as right before calling the constructor), the Win32 HWND will not exist yet, so the native DLL calls will fail. If you call it too late (such as after the window is shown), the DLL calls will have no effect on Windows. - -Add an event handler for the `SourceInitialized` event and call the aforementioned DarkNet method. - -```cs -window.SourceInitialized += (_, _) => { DarkNet.SetDarkModeAllowedForWindow(window, true); }; -``` - -You must also perform this step for all subsequent windows you show in your application, not just the first window. - -### Complete example - -```xaml - - -``` - -```cs -public partial class App: Application { - - private void App_OnStartup(object sender, StartupEventArgs e) { - DarkNet.SetDarkModeAllowedForProcess(true); - - Window window = new MainWindow(); - window.SourceInitialized += (_, _) => { DarkNet.SetDarkModeAllowedForWindow(window, true); }; - - window.Show(); - } - -} -``` - -## Demo - -Download and run `darknet-demo-wpf.exe` from the [latest release](https://github.com/Aldaviva/DarkNet/releases). - -[![WPF window with dark title bar](https://i.imgur.com/yWWyS0Jl.png)](https://i.imgur.com/yWWyS0J.png) - -You can also clone this repository and build the `darknet-demo-wpf` project yourself using Visual Studio Community 2019. - -## Limitations -- This library currently requires a fairly recent version of Windows and the .NET runtime. This could be relaxed in a future version to allow inclusion in applications that run in a wider range of environments. -- This library currently does not expose whether the active Windows app mode is set to Dark or Light. This may be possible to add in a future version to allow you to implement a "follow Windows app mode" strategy in your application. -- This library only changes the theme of the title bar/window chrome/non-client area, as well as the system context menu (the menu that appears when you right click on the title bar, or left click on the title bar icon, or hit `Alt`+`Space`). It does not change the theme of the client area of your window. It is up to you to make that look different when dark mode is enabled. -- This library currently does not help you persist a user choice for the mode they want your application to use. You can expose an option and persist that yourself, then pass the desired value to the methods in this library (*e.g.* call `DarkNet.SetDarkModeAllowedForProcess(false)` for light mode, or just don't call it at all). - -## Acknowledgements - -- [Milan Burda](https://github.com/miniak) for explaining how to add this in Electron ([electron/electron #23479: [Windows] Title bar does not respect dark mode](https://github.com/electron/electron/issues/23479)) -- [dyasta](https://stackoverflow.com/users/191514/dyasta) for [an explanation on Stack Overflow](https://stackoverflow.com/a/58547831/979493) -- [Richard Yu](https://github.com/ysc3839) for [implementing this in a C++ library](https://github.com/ysc3839/win32-darkmode) -- [Berrysoft](https://github.com/Berrysoft) for [implementing this in Mintty, the Cygwin Terminal emulator](https://github.com/mintty/mintty/issues/983) ([further discussion](https://github.com/mintty/mintty/pull/984)) \ No newline at end of file diff --git a/Readme.md b/Readme.md index dc4a8d8..6ac02dd 100644 --- a/Readme.md +++ b/Readme.md @@ -1,28 +1,22 @@ DarkNet === -This is a .NET library that you can use to enable Windows 10's dark mode for your application's title bars and system context menus, similar to the dark title bar in Command Prompt. +[![Nuget](https://img.shields.io/nuget/v/DarkNet?logo=nuget)](https://www.nuget.org/packages/DarkNet/) [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/Aldaviva/DarkNet/.NET?logo=github)](https://github.com/Aldaviva/DarkNet/actions/workflows/dotnetpackage.yml) -![WPF window with dark title bar](https://i.imgur.com/yWWyS0J.png) +Enable native Windows dark mode for your WPF and Windows Forms title bars. - +![WPF window with dark title bar](https://raw.githubusercontent.com/Aldaviva/DarkNet/master/.github/images/demo-wpf.png) + + - [Requirements](#requirements) - [Installation](#installation) +- [Usage](#usage) + - [Basics](#basics) - [WPF](#wpf) - [Windows Forms](#windows-forms) -- [Usage](#usage) - - [WPF](#wpf-1) - - [On application startup](#on-application-startup) - - [Before showing a new window](#before-showing-a-new-window) - - [Complete example](#complete-example) - - [Windows Forms](#windows-forms-1) - - [On application startup](#on-application-startup-1) - - [Before showing a new window](#before-showing-a-new-window-1) - - [Complete example](#complete-example-1) -- [Demo](#demo) - - [WPF](#wpf-2) - - [Windows Forms](#windows-forms-2) + - [Taskbar theme](#taskbar-theme) +- [Demos](#demos) - [Limitations](#limitations) - [Acknowledgements](#acknowledgements) @@ -31,34 +25,64 @@ This is a .NET library that you can use to enable Windows 10's dark mode for you ## Requirements -- .NET Framework 4.7.2 or later, or .NET Core 3.1 or later -- Windows 10 version 1809 (October 2018 Update) or later for dark mode to work - - You can run your app on earlier Windows versions, but the title bar won't turn dark -- WPF or Windows Forms +- .NET runtime + - .NET 5.0 or later + - .NET Core 3.1 or later + - .NET Framework 4.5.2 or later +- Windows + - Windows 10 version 1809 (October 2018 Update) or later + - Windows 11 or later + - You can also run your app on earlier Windows versions as well, but the title bar won't turn dark. +- Windows Presentation Foundation or Windows Forms ## Installation -There are two packages available, one for WPF and one for Windows Forms. You can install the one that corresponds to the GUI technology that your program uses. +[DarkNet is available on NuGet Gallery](https://www.nuget.org/packages/DarkNet/). - -### WPF ```ps1 -dotnet add package DarkNet-WPF +dotnet add package DarkNet ``` -[See **DarkNet-WPF** on NuGet](https://www.nuget.org/packages/DarkNet-WPF/) - - -### Windows Forms ```ps1 -dotnet add package DarkNet-Forms +Install-Package DarkNet ``` -[See **DarkNet-Forms** on NuGet](https://www.nuget.org/packages/DarkNet-Forms/) ## Usage - + +### Basics + +#### Entry point + +The top-level interface of this library is Dark.Net.IDarkNet, which is implemented by the **`DarkNet`** class. A shared instance of this class is available from **`DarkNet.Instance`**, or you can construct a new instance with `new DarkNet()`. + +#### Methods + +You must call **both** of the methods below to make a window use the dark theme. See the following sections for specific examples. + +1. First, call **`SetCurrentProcessTheme()`** to allow your process to change the themes of its windows, although it doesn't apply any themes on its own. + + If you don't call this method, all windows in your application will use the light theme, even if you call `SetWindowTheme*`. +2. Next, call **`SetWindowTheme*()`** to actually apply the theme to each window. There are 3 methods to handle WPF, Forms, and raw `HWND` handles. + + If you don't call one of these methods on a given window, that window will use the light theme, even if you called `SetCurrentProcessTheme`. + +#### Themes + +This library uses the `Theme` enum to differentiate **`Dark`** mode from **`Light`** mode. You can set any window in your application to use whichever theme you want, they don't all have to be the same theme. There is also an **`Auto`** value that allows the theme to be inherited from a higher level, falling back from the window to the process to the user account: + +1. When a **window's** theme is `Dark` or `Light`, it uses that theme directly (`SetWindowTheme*`). +2. When a **window's** theme is `Auto`, it inherits from the **process's** theme set by `SetCurrentProcessTheme`. +3. When a **window and its process's** themes are both `Auto`, they inherit from the local user's operating system **default app theme** preferences (Settings › Personalization › Colors › Choose your default app mode). + +##### Live updates + +If the user changes their default app mode while your app is running with the `Auto` theme, then your app will automatically update and render the new theme without having to handle any events or restart. + +Try the [demo apps](#demos) to see this behavior in action. + + ### WPF You must do both of the following steps. @@ -68,29 +92,22 @@ You must do both of the following steps. Before showing **any** windows in your application, you must call ```cs -darknet.wpf.DarkNet.SetDarkModeAllowedForProcess(true); +IDarkNet.SetCurrentProcessTheme(theme); ``` -A good place to call this is in an event handler for your Application's `Startup` event. - -By default, WPF apps have an `App.xaml` file that specifies the first `Window` to show in the `StartupUri` attribute: -```xaml - -``` +A good place to call this is in an event handler for your Application's **`Startup`** event, or in its overridden **`OnStartup`** method. -This does not allow you to run custom code before showing the window, so remove `StartupUri` and add `Startup`, which specifies an event handler method. -```xaml - -``` +By default, WPF apps have an `App.xaml.cs` class that inherits from `Application`. You can override `OnStartup` to initialize DarkNet: -In `App.xaml.cs`, create a method to handle this event and call the aforementioned DarkNet method. ```cs -public partial class App: Application { +// App.xaml.cs +using Dark.Net; - private void App_OnStartup(object sender, StartupEventArgs e) { - DarkNet.SetDarkModeAllowedForProcess(true); +public partial class App: Application { - // Incomplete example; see below for complete example including showing your first window. + protected override void OnStartup(StartupEventArgs e) { + base.OnStartup(e); + DarkNet.Instance.SetCurrentProcessTheme(Theme.Auto); } } ``` @@ -98,49 +115,31 @@ public partial class App: Application { #### Before showing a new window -Before showing each window in your application, you have to enable dark mode for that window. +Before showing each window in your application, you have to set the theme for that window. ```cs -darknet.wpf.DarkNet.SetDarkModeAllowedForWindow(window, true); +IDarkNet.SetWindowThemeWpf(window, theme); ``` -You must do this when the `SourceInitialized` event is fired for the window you are about to show. If you do it before this (such as right before calling the constructor), the Win32 HWND will not exist yet, so the native DLL calls will fail. If you call it too late (such as after the window is shown), the DLL calls will have no effect on Windows. +A good place to call this is in the window's constructor **after the call to `InitializeComponent`**, or in an event handler for the window's **`SourceInitialized`** event. -Add an event handler for the `SourceInitialized` event and call the aforementioned DarkNet method. +If you call it too late (such as after the window is shown), the calls will have no effect on Windows. ```cs -window.SourceInitialized += (_, _) => { DarkNet.SetDarkModeAllowedForWindow(window, true); }; -``` - -You must also perform this step for all subsequent windows you show in your application, not just the first window. - - -#### Complete example - -```xaml - - -``` +// MainWindow.xaml.cs +public partial class MainWindow { -```cs -public partial class App: Application { - - private void App_OnStartup(object sender, StartupEventArgs e) { - DarkNet.SetDarkModeAllowedForProcess(true); - - Window window = new MainWindow(); - window.SourceInitialized += (_, _) => { DarkNet.SetDarkModeAllowedForWindow(window, true); }; - - window.Show(); + public MainWindow() { + InitializeComponent(); + DarkNet.Instance.SetWindowThemeWpf(this, Theme.Auto); } } ``` - +You must perform this step for **every** window you show in your application, not just the first one. + + ### Windows Forms You must do both of the following steps. @@ -150,17 +149,20 @@ You must do both of the following steps. Before showing **any** windows in your application, you must call ```cs -darknet.forms.DarkNet.SetDarkModeAllowedForProcess(true); +IDarkNet.SetCurrentProcessTheme(theme); ``` -A good place to call this is at the top of the `Main()` method of your application. +A good place to call this is at the **start of the `Main()` method** of your application. ```cs +// Program.cs +using Dark.Net; + internal static class Program { [STAThread] private static void Main() { - DarkNet.SetDarkModeAllowedForProcess(true); + DarkNet.Instance.SetCurrentProcessTheme(Theme.Auto); // Incomplete example; see below for complete example including showing your first window. } @@ -170,73 +172,75 @@ internal static class Program { #### Before showing a new window -Before showing each window in your application, you have to enable dark mode for that window. +Before showing each window in your application, you have to set the theme for that window. ```cs -darknet.forms.DarkNet.SetDarkModeAllowedForWindow(window, true); +IDarkNet.SetWindowThemeForms(window, theme); ``` -You must do this before showing the window with `Show()` or `Application.Run()`. If you call it too late (such as after the window is shown), the DLL calls will have no effect on Windows. - -Call the aforementioned DarkNet method after the window is constructed. +You must do this **before calling `Show()` or `Application.Run()`** to show the window. If you call it too late (such as after the window is shown), the calls will have no effect on Windows. ```cs Form mainForm = new Form1(); -DarkNet.SetDarkModeAllowedForWindow(mainForm, true); +DarkNet.Instance.SetWindowThemeForms(mainForm, Theme.Auto); ``` -You must also perform this step for all subsequent windows you show in your application, not just the first window. +You must perform this step for **every** window you show in your application, not just the first one. - + #### Complete example ```cs +// Program.cs +using Dark.Net; + internal static class Program { [STAThread] private static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - DarkNet.SetDarkModeAllowedForProcess(true); + DarkNet.Instance.SetCurrentProcessTheme(Theme.Auto); Form mainForm = new Form1(); - DarkNet.SetDarkModeAllowedForWindow(mainForm, true); - mainForm.Show(); + DarkNet.Instance.SetWindowThemeForms(mainForm, Theme.Auto); Application.Run(mainForm); } } ``` - - -## Demo + +### Taskbar theme - -### WPF +Windows introduced a preference to choose a dark or light taskbar in Windows 10 version 1903. This is controlled by Settings › Personalization › Colors › Choose your default Windows mode. -Download and run `darknet-demo-wpf.exe` from the [latest release](https://github.com/Aldaviva/DarkNet/releases). +DarkNet exposes the value of this preference with the IDarkNet.UserTaskbarThemeIsDark property, as well as the change event IDarkNet.UserTaskbarThemeIsDarkChanged. You can use these to render a tray icon in the notification area that matches the taskbar's theme, and re-render it when the user preference changes. -![WPF window with dark title bar](https://i.imgur.com/yWWyS0J.png) + +## Demos -You can also clone this repository and build the `darknet-demo-wpf` project yourself using Visual Studio Community 2019. +You can download the following precompiled demos, or clone this repository and build the demo projects yourself using Visual Studio Community 2022. - -### Windows Forms + +#### WPF -Download and run `darknet-demo-forms.exe` from the [latest release](https://github.com/Aldaviva/DarkNet/releases). +Download and run `darknet-demo-wpf.exe` from the [latest release](https://github.com/Aldaviva/DarkNet/releases). + +![WPF window with dark title bar](https://raw.githubusercontent.com/Aldaviva/DarkNet/master/.github/images/demo-wpf.png) + + +#### Windows Forms -![Windows Forms window with dark title bar](https://i.imgur.com/3PDwxTy.png) +Download and run `darknet-demo-winforms.exe` from the [latest release](https://github.com/Aldaviva/DarkNet/releases). -You can also clone this repository and build the `darknet-demo-forms` project yourself using Visual Studio Community 2019. +![Windows Forms window with dark title bar](https://raw.githubusercontent.com/Aldaviva/DarkNet/master/.github/images/demo-winforms.png) ## Limitations -- This library currently requires a fairly recent version of Windows and the .NET runtime. This could be relaxed in a future version to allow inclusion in applications that run in a wider range of environments. -- This library currently does not expose whether the active Windows app mode is set to Dark or Light. This may be possible to add in a future version to allow you to implement a "follow Windows app mode" strategy in your application. - This library only changes the theme of the title bar/window chrome/non-client area, as well as the system context menu (the menu that appears when you right click on the title bar, or left click on the title bar icon, or hit `Alt`+`Space`). It does not change the theme of the client area of your window. It is up to you to make that look different when dark mode is enabled. -- This library currently does not help you persist a user choice for the mode they want your application to use. You can expose an option and persist that yourself, then pass the desired value to the methods in this library (*e.g.* call `DarkNet.SetDarkModeAllowedForProcess(false)` for light mode, or just don't call it at all). +- This library currently does not help you persist a user choice for the mode they want your application to use across separate process executions. You can expose an option and persist that yourself, then pass the desired `Theme` value to the methods in this library. ## Acknowledgements diff --git a/darknet-demo-winforms/darknet-demo-winforms.csproj b/darknet-demo-winforms/darknet-demo-winforms.csproj index 070234a..fd5ef25 100644 --- a/darknet-demo-winforms/darknet-demo-winforms.csproj +++ b/darknet-demo-winforms/darknet-demo-winforms.csproj @@ -63,16 +63,16 @@ - - - 2.0.18.2 - - {8f367ec7-f0f9-4b96-8793-91f06ac78320} DarkNet + + + 2.1.18 + + \ No newline at end of file diff --git a/darknet.sln b/darknet.sln index dcb582f..3d11b9e 100644 --- a/darknet.sln +++ b/darknet.sln @@ -7,7 +7,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "darknet-demo-winforms", "da EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DarkNet", "darknet\DarkNet.csproj", "{8F367EC7-F0F9-4B96-8793-91F06AC78320}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "darknet-demo-wpf", "darknet-demo-wpf\darknet-demo-wpf.csproj", "{93007D62-828D-48B7-AA61-3B2B6CD82981}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "darknet-demo-wpf", "darknet-demo-wpf\darknet-demo-wpf.csproj", "{93007D62-828D-48B7-AA61-3B2B6CD82981}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,8 +21,8 @@ Global {F1BBA11A-E333-43D4-936E-F4C53B9AEE19}.Release|Any CPU.Build.0 = Release|Any CPU {8F367EC7-F0F9-4B96-8793-91F06AC78320}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8F367EC7-F0F9-4B96-8793-91F06AC78320}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8F367EC7-F0F9-4B96-8793-91F06AC78320}.Release|Any CPU.ActiveCfg = Debug|Any CPU - {8F367EC7-F0F9-4B96-8793-91F06AC78320}.Release|Any CPU.Build.0 = Debug|Any CPU + {8F367EC7-F0F9-4B96-8793-91F06AC78320}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8F367EC7-F0F9-4B96-8793-91F06AC78320}.Release|Any CPU.Build.0 = Release|Any CPU {93007D62-828D-48B7-AA61-3B2B6CD82981}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93007D62-828D-48B7-AA61-3B2B6CD82981}.Debug|Any CPU.Build.0 = Debug|Any CPU {93007D62-828D-48B7-AA61-3B2B6CD82981}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/darknet/DarkNet.cs b/darknet/DarkNet.cs index 9cb91be..6ab6e49 100644 --- a/darknet/DarkNet.cs +++ b/darknet/DarkNet.cs @@ -128,6 +128,15 @@ private static bool IsWindowVisible(IntPtr windowHandle) { return (windowInfo.dwStyle & WindowStyles.WsVisible) != 0; } + /// + public void SetWindowThemeRaw(IntPtr windowHandle, Theme theme) { + if (IsWindowVisible(windowHandle)) { + throw new InvalidOperationException($"Called {nameof(SetWindowThemeRaw)}() too late, call it before the window is visible."); + } + + SetModeForWindow(windowHandle, theme); + } + /// /// call this after creating but before showing a window, such as WPF's Window.OnSourceInitialized or Forms' Form.Load /// if window.Visibility==VISIBLE and WindowPlacement.ShowCmd == SW_HIDE (or whatever), it was definitely called too early diff --git a/darknet/DarkNet.csproj b/darknet/DarkNet.csproj index bf8da8f..9f8ece6 100644 --- a/darknet/DarkNet.csproj +++ b/darknet/DarkNet.csproj @@ -13,7 +13,7 @@ Ben Hutchison Ben Hutchison © 2022 Ben Hutchison - Make WPF and Windows Forms title bars dark in Windows ≥ 10. + Enable native Windows dark mode for your WPF and Windows Forms title bars. Apache-2.0 https://github.com/Aldaviva/DarkNet dark dark-mode darkmode forms winforms wpf diff --git a/darknet/IDarkNet.cs b/darknet/IDarkNet.cs index 719ccf8..6656f08 100644 --- a/darknet/IDarkNet.cs +++ b/darknet/IDarkNet.cs @@ -48,6 +48,18 @@ public interface IDarkNet: IDisposable { /// If this method was called too late (such as after calling returns). void SetWindowThemeForms(Form window, Theme theme); + /// + /// Turn on dark mode for a window. + /// This method is a lower-level alternative to and for use when one of the windows in your application was created neither by WPF nor Windows Forms, but you still want to make its title bar dark. + /// You must have already called before creating this window. + /// You must call this method before the window is visible. + /// + /// The correct time to call this method is when the window has already been constructed, but it has not yet been shown (i.e. its Win32 window style must not be visible yet). + /// A HWND handle to a Win32 window, which has been constructed but has not yet been shown. + /// The theme to use for this window, or to inherit from the process (defined by the theme passed to ) or the user's default app settings if you also set the app to (defined in Settings › Personalization › Colors). + /// If this method was called too late. + void SetWindowThemeRaw(IntPtr windowHandle, Theme theme); + /// /// Whether windows that follow the user's default operating system theme, such as Windows Explorer, Command Prompt, and Settings, will use dark mode in their title bars, context menus, and other themed areas. Also known as "app mode" or "default app mode". /// This reflects the user's preference in Settings › Personalization › Colors › Choose your default app mode.