我无法根据我的屏幕布局获得按钮布局(使用按钮进行模拟).

enter image description here

我希望能够获得与屏幕上现有按钮类似的相同分布的按钮.

相反,我得到了这样的结论:

enter image description here

我目前的脚本是这样的(这是我实现的最接近最终布局的脚本):

using System.Runtime.InteropServices;

namespace WebStack_Deployer_for_Docker.Forms
{
    public partial class DisplaySelector : Form
    {
        private const int SW_SHOWNORMAL = 1;

        [StructLayout(LayoutKind.Sequential)]
        private struct POINT
        {
            public int X;
            public int Y;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct RECT
        {
            public int Left;
            public int Top;
            public int Right;
            public int Bottom;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct WINDOWPLACEMENT
        {
            public int length;
            public int flags;
            public int showCmd;
            public POINT ptMinPosition;
            public POINT ptMaxPosition;
            public RECT rcNormalPosition;
        }

        public readonly int screens = Screen.AllScreens.Length;

        private Panel panelMonitors;

        private const int ButtonWidth = 160;

        private const int ButtonHeight = 100;

        public DisplaySelector()
        {
            InitializeComponent();
            InitializeMonitorSelection();
        }

        private void InitializeMonitorSelection()
        {
            panelMonitors = new Panel
            {
                Dock = DockStyle.Fill
            };
            Controls.Add(panelMonitors);

            Screen[] allScreens = Screen.AllScreens;
            Array.Sort(allScreens, (a, b) => a.Bounds.X.CompareTo(b.Bounds.X));

            int totalScreens = allScreens.Length;

            int startX = (ClientSize.Width - totalScreens * (ButtonWidth + 20)) / 2;
            int startY = (ClientSize.Height - ButtonHeight) / 2;

            for (int i = 0; i < totalScreens; i++)
            {
                int monitorIndex = i;
                Button monitorButton = new Button
                {
                    Text = $"Pantalla #{monitorIndex + 1}",
                    Tag = monitorIndex,
                    Size = new Size(ButtonWidth, ButtonHeight),
                    // Calcula la posición X y Y del botón de acuerdo a la disposición real de las pantallas
                    Location = CalculateButtonPosition(startX, startY, monitorIndex, totalScreens, ButtonWidth)
                };

                monitorButton.Click += MonitorButton_Click;
                panelMonitors.Controls.Add(monitorButton);
            }
        }

        private Point CalculateButtonPosition(int startX, int startY, int monitorIndex, int totalScreens, int buttonWidth)
        {

            int positionX = startX;
            if (monitorIndex > 0)
            {
                positionX += (buttonWidth + 20) * monitorIndex;
            }

            int positionY = startY;

            if (totalScreens > 1)
            {
                if (monitorIndex == 0)
                {
                    positionY -= ButtonHeight / 2 + 20;
                }
                else if (monitorIndex == totalScreens - 1)
                {
                    positionY += ButtonHeight / 2 + 20;
                }
            }

            return new Point(positionX, positionY);
        }

        private void MonitorButton_Click(object sender, EventArgs e)
        {
            Button clickedButton = (Button)sender;
            int monitorIndex = (int)clickedButton.Tag;
            MessageBox.Show($"Has seleccionado el Monitor #{monitorIndex}");
            MoveToDisplay(monitorIndex);
        }

        public static void MoveToDisplay(int display)
        {
            if (Screen.AllScreens.Length > 1)
            {
                IntPtr windowHandle = GetForegroundWindow();

                WINDOWPLACEMENT placement = new WINDOWPLACEMENT
                {
                    length = Marshal.SizeOf(typeof(WINDOWPLACEMENT))
                };
                GetWindowPlacement(windowHandle, ref placement);

                Rectangle secondMonitor = Screen.AllScreens[display].WorkingArea;
                int windowWidth = placement.rcNormalPosition.Right - placement.rcNormalPosition.Left;
                int windowHeight = placement.rcNormalPosition.Bottom - placement.rcNormalPosition.Top;
                int windowLeft = secondMonitor.Left + (secondMonitor.Width - windowWidth) / 2;
                int windowTop = secondMonitor.Top + (secondMonitor.Height - windowHeight) / 2;

                placement.rcNormalPosition.Left = windowLeft;
                placement.rcNormalPosition.Top = windowTop;
                placement.rcNormalPosition.Right = windowLeft + windowWidth;
                placement.rcNormalPosition.Bottom = windowTop + windowHeight;
                placement.showCmd = SW_SHOWNORMAL;

                SetWindowPlacement(windowHandle, ref placement);
            }
        }

        [DllImport("user32.dll")]
        private static extern IntPtr GetForegroundWindow();

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool SetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
    }
}

100我用C#/WinForm在Windows 11家用PC上为.net8.0编写了这个程序.

100

  • 按钮的顺序.
  • 与系统中建立的配置类似的按钮布局.

推荐答案

这可以简化为不使用P/Invoke.您可以try 的一种解决方案是创建一个表示多屏幕工作区的最大边界的矩形,并对其进行zoom 以适应TableLayoutPanel的单元格.通过将其设置为Anchor.None,它将自动在TLP的单个单元格中居中,在该点可以添加表示各个屏幕的按钮.

windows control panel next to custom version

当单击其中一个按钮时,将已单击的显示的位置和大小复制到主窗体.


public partial class DisplaySelectorForm : Form
{
    public DisplaySelectorForm() => InitializeComponent();
    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        var screens = Screen.AllScreens;

        var minLeft = screens.Min(screen => screen.WorkingArea.Left);
        var minTop = screens.Min(screen => screen.WorkingArea.Top);
        var maxRight = screens.Max(screen => screen.WorkingArea.Right);
        var maxBottom = screens.Max(screen => screen.WorkingArea.Bottom);
        var workspace = new Rectangle
        {
            Width = maxRight - minLeft,
            Height = maxBottom - minTop,
        };
        var scaleX = tableLayoutPanel.Width / (double)workspace.Width;
        var scaleY = tableLayoutPanel.Height / (double)workspace.Height;
        var scale = Math.Min(scaleX, scaleY); 
        workspace.Size = new Size((int)(workspace.Width * scale), (int)(workspace.Height * scale));

        var workspacePanel = new Panel
        {
            Size = workspace.Size,
            Anchor = AnchorStyles.None,
            BackColor = Color.White,
        };

        tableLayoutPanel.Controls.Add(workspacePanel, 0, 0);

        foreach (var screen in screens)
        {
            var screenLeft = screen.WorkingArea.Left - minLeft;
            var screenTop = screen.WorkingArea.Top - minTop;
            var screenScaledLeft = (int)(screenLeft * scale);
            var screenScaledTop = (int)(screenTop * scale);
            var screenScaledWidth = (int)(screen.WorkingArea.Width * scale);
            var screenScaledHeight = (int)(screen.WorkingArea.Height * scale);
            var screenButton = new Button
            {
                Text = $"Display {screens.ToList().IndexOf(screen) + 1}\n({screen.DeviceName})",
                Size = new Size(screenScaledWidth, screenScaledHeight),
                Location = new Point(screenScaledLeft, screenScaledTop),
                BackColor = screen.Primary ? Color.CornflowerBlue : Color.LightGray,
                Tag = screen,
            };
            screenButton.Click += (sender, e) =>
            {
                if(
                    sender is Control control
                    &&
                    control.Tag is Screen selectedScreen
                    &&
                    Owner is MainForm mainForm)
                {
                    mainForm.Location = selectedScreen.WorkingArea.Location;
                    mainForm.Size = selectedScreen.WorkingArea.Size;
                    DialogResult = DialogResult.OK;
                }
            };
            workspacePanel.Controls.Add(screenButton);
        }
    }
}

主窗体代码使用一种技术将DisplaySelectorForm显示为闪屏,如此answer中所述.

public partial class MainForm : Form
{
    public MainForm()
    {
        InitializeComponent();
        _ = Handle;
        BeginInvoke(new Action(() => execDisplaySelectionFlow()));
    }
    protected override void SetVisibleCore(bool value) =>
        base.SetVisibleCore(value && _initialized);

    private void execDisplaySelectionFlow()
    {
        using (var selector = new DisplaySelectorForm())
        {
            selector.ShowDialog(this);
        }
        _initialized = true;
        WindowState = FormWindowState.Maximized;
        Show();
    }
    bool _initialized = false;
}

Csharp相关问答推荐

禁用AutoSuggestBox项目更改时的动画?

有没有一种方法可以在包含混合文本的标签中嵌入超链接?

为什么EF Core 6会针对null验证Count(*)?

找不到网址:https://localhost:7002/Category/Add?区域= Admin.为什么我的URL是这样生成的?area = Admin而不是/Admin/

在C#WinUI中,一个关于System的崩溃."由于未知原因导致执行不例外"

在多对多关系上不删除实体

如何在NodaTime中为Instant添加一年?

Thad.Sept()vs Task.Delay().Wait()

Cosmos SDK和Newtonsoft对静态只读记录的可能Mutations

如果设置了另一个属性,则Newtonsoft JSON忽略属性

在字符串C#之前获取数字

MSTest--将消息直接写入StdOut和使用TestContext有什么不同?

WPF动态设置弹出窗口水平偏移

从GRPC连接创建ZipArchive

毛伊岛.NET 8图片不再适合按钮

DropDownListFor未显示选定值

Xamarin中出错.表单:应用程序的分部声明不能指定不同的基类

实体框架允许您具有筛选的属性吗?

在c#中,使用Okta和Blazor时,LocalReDirect()陷入循环,出现错误&请求太多.

MS Project读取项目自定义域