Search code examples
c#winformswinapi

Is there a way to refresh/update the Screen object in a Win Forms application?


Is there a way to update the Screen object? Remark: Screen object like in (Screen s in Screen.AllScreens)

The object does not change if the scaling or other properties are changed. A very detailed analysis is to be found here: Windows Screens.AllScreens does not update after switching the main display

In the comments it looked like sending a broadcast PostMessage((IntPtr)HWND_BROADCAST, WM_DISPLAYCHANGE, UIntPtr.Zero, IntPtr.Zero) would cause an update of the screen object.

I have tried that, but my screen objects do not update. Is there a way to force an update?

The trouble is the caching static field not being "nulled" correctly in all situations.

public unsafe static Screen[] AllScreens
{
  get
      {
        if (s_screens is null)
           {
              if (s_multiMonitorSupport)

Actually sending the request should null the cache

private static void OnDisplaySettingsChanging(object? sender, EventArgs e)
{
    // Now that we've responded to this event, we don't need it again until
    // someone re-queries. We will re-add the event at that time.
    SystemEvents.DisplaySettingsChanging -= new EventHandler(OnDisplaySettingsChanging);

    // Display settings changed, so the set of screens we have is invalid.
    s_screens = null;
}

Solution

  • Thanks to the help of several people (comments) I have a (currently) working solution.

    What works best for me is to call the private(!) callback method of Screen, which is invoked when the SettingsChanging event occurs. SettingsChanging ONLY is insufficient as other changes (e.g. Scaling) pass unnoticed.

    Needless to say that kind of solution is a hack.

    MethodInfo? method = typeof(Screen).GetMethod("OnDisplaySettingsChanging", BindingFlags.Static | BindingFlags.NonPublic);
    Debug.Assert(method != null, "Method OnDisplaySettingsChanging not found");
    method.Invoke(obj: null, parameters: new object[] { "dummy object", EventArgs.Empty });
    

    Sending the broadcast of WM_DISPLAYCHANGE also works, but less reliable for me. It is asynchronous and I have to wait until the change occurred first. Also I force a lot of Screen objects in other applications to update as well (side effects ???).

    Due to the nature of the broadcast it is also impossible to determine in my application's WM_DISPLAYCHANGE handling if the change is caused "by me" or external reasons.

    Simon Mourier's complete example is here: https://pastebin.com/raw/4DBXthUS

    In the other question Windows Screens.AllScreens does not update after switching the main display it also has a comment, so if going with

    PostMessage(HWND_BROADCAST, WM_DISPLAYCHANGE, IntPtr.Zero, IntPtr.Zero);
                   
    

    it is probably better to use SendMessageTimeout()

    It is sent message, that makes using SendMessageTimeout() instead of PostMessage() appropriate. – Hans Passant

    Thanks again for all the help.


    SM code snippet in case the pastebin expires:

    using System;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    using Microsoft.Win32;
    namespace WinFormsApp1
    {
        class Program
        {
            [STAThread]
            static void Main()
            {
                ApplicationConfiguration.Initialize();
                Application.Run(new MyForm());
            }
        }
    
        public partial class MyForm : Form
        {
            public MyForm()
            {
                SystemEvents.DisplaySettingsChanged += (s, e) => MessageBox.Show("DisplaySettingsChanged");
                var button = new Button { Text = "Click me" };
                button.Click += (s, e) => PostMessage(HWND_BROADCAST, WM_DISPLAYCHANGE, IntPtr.Zero, IntPtr.Zero);
                Controls.Add(button);
            }
    
            private static readonly IntPtr HWND_BROADCAST = new(0xFFFF);
            private const int WM_DISPLAYCHANGE = 126;
    
            [DllImport("user32.dll")]
            private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam);
        }
    }