カーソル位置の取得がディスプレイの拡大縮小によりズレる

NifrelTV 0 評価のポイント
2025-03-03T19:05:01.06+00:00

Visual Studio 2020 でプログラムを始めてみた初心者です。
GetCursorPos 関数を使い、カーソル位置の色情報を取得させています。

ディスプレイの拡大縮小が100%では一致するのですが、拡大縮小するとズレます。
4Kモニタで125%表示させると、かなり左上の位置を拾います。
ディスプレイの拡大縮小に対応するためにはどういった対処が効くでしょうか。

こうしたらできる!

という情報がありましたら、お教えください。

AIに聞いて以下を入れてみましたが、挙動に変化がありません。

        Point cursorPosition;

        GetCursorPos(out cursorPosition);

        IntPtr monitor = MonitorFromPoint(cursorPosition, 2);

        GetDpiForMonitor(monitor, 0, out uint dpiX, out uint dpiY);

        float scaleX = 1.0f;

        float scaleY = 1.0f;

        if (dpiX != 96)

        {

            scaleX = dpiX / 96.0f;

        }

        if (dpiY != 96)

        {

            scaleY = dpiY / 96.0f;

        }

        int correctedX = (int)(cursorPosition.X * scaleX);

        int correctedY = (int)(cursorPosition.Y * scaleY);

        Point correctedLocation = new Point(correctedX, correctedY);

以上、よろしくお願いします。

Visual Studio
Visual Studio
Windows、Web、モバイル デバイス用のアプリケーションを構築するための統合開発ツールの Microsoft スイートのファミリ。
128 件の質問
{count} 件の投票

2 件の回答

並べ替え方法: 最も役に立つ
  1. Deleted

    この回答は当社の行動規範に違反したため削除されました。 アクションを実行する前にこの回答を手動で報告したか、自動検出機能により特定しました。 詳細については、当社の行動規範を参照してください。


    コメントはオフになっています。 詳細情報

  2. gekka 11,456 評価のポイント MVP
    2025-03-05T03:52:02.3433333+00:00

    プライマリのモニタの拡大率に対してセカンダリ以降の拡大率が大きいか小さいかで計算式が変わるっぽい。

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.Drawing;
    using System.Windows.Forms;
    
    namespace WinFormsApp1
    {
        public partial class Form1 : Form
        {
            private PictureBox pictureBoxRange;
            private PictureBox pictureBoxFill;
    
            private Label label1;
            private Label label2;
    
            private Timer timer1;
    
            private const int picW = 64;
            private const int picH = 64;
    
            public Form1()
            {
                this.StartPosition = FormStartPosition.Manual;
                this.Left = 0;
                this.Top = 0;
    
                this.AutoScaleMode = AutoScaleMode.Dpi;
    
                this.pictureBoxRange = new PictureBox() { Width = picW, Height = picH, SizeMode = PictureBoxSizeMode.Normal, Top = 10, BorderStyle = BorderStyle.FixedSingle };
                this.pictureBoxRange.Left = 10;
                this.Controls.Add(this.pictureBoxRange);
    
                this.pictureBoxFill = new PictureBox() { Width = picW, Height = picH, SizeMode = PictureBoxSizeMode.Normal, Top = 10, BorderStyle = BorderStyle.FixedSingle };
                this.pictureBoxFill.Left = pictureBoxRange.Right + 10;
                this.Controls.Add(this.pictureBoxFill);
    
                this.label1 = new Label() { AutoSize = true, Left = 10, Top = this.pictureBoxRange.Bottom + 2 };
                this.label2 = new Label() { AutoSize = true, Left = 10, Top = this.label1.Bottom + 2 };
                this.Controls.Add(this.label1);
                this.Controls.Add(this.label2);
    
    
                this.ClientSize = new Size(300, this.label2.Bottom + 5);
    
                this.timer1 = new Timer();
                this.timer1.Tick += (s, e) => Test();
                this.timer1.Interval = 100;
                this.timer1.Start();
            }
    
            private Point CalcCapturePoint(Point p1)
            {
                // nugetでCSWin32を入れる
                // NativeMethods.txtに EnumDisplaySettings を記入しておく
    
                var screen = System.Windows.Forms.Screen.FromPoint(p1);
    
                var devmode = new Windows.Win32.Graphics.Gdi.DEVMODEW();
                Windows.Win32.PInvoke.EnumDisplaySettings(screen.DeviceName, Windows.Win32.Graphics.Gdi.ENUM_DISPLAY_SETTINGS_MODE.ENUM_CURRENT_SETTINGS, ref devmode);
    
                var scaleX = (double)devmode.dmPelsWidth / screen.Bounds.Width;
                var scaleY = (double)devmode.dmPelsHeight / screen.Bounds.Height;
    
                if (scaleX > 1)
                {
                    var x2 = screen.Bounds.Left + (p1.X - screen.Bounds.Left) * scaleX;
                    var y2 = screen.Bounds.Top + (p1.Y - screen.Bounds.Top) * scaleY;
                    return new Point((int)x2, (int)y2);
                }
                else
                {
                    var x2 = p1.X * scaleX;
                    var y2 = p1.Y * scaleY;
                    return new Point((int)x2, (int)y2);
                }
            }
    
            private void Test()
            {
                pictureBoxRange.Image?.Dispose();
                pictureBoxRange.Image = null;
    
                pictureBoxFill.Image?.Dispose();
                pictureBoxFill.Image = null;
    
                Point p1 = Cursor.Position;
                Point p2 = CalcCapturePoint(Cursor.Position);
    
                System.Drawing.Bitmap bmpRange = new Bitmap(this.pictureBoxRange.Width, this.pictureBoxRange.Height);
                using (var g = System.Drawing.Graphics.FromImage(bmpRange))
                {
                    //カーソル下の色をビットマップにコピー
                    g.CopyFromScreen(p2, System.Drawing.Point.Empty, new Size(bmpRange.Width, bmpRange.Height));
                }
                this.pictureBoxRange.Image = bmpRange;
    
                ////コピーされた1ピクセルの色を取得
                Color color = bmpRange.GetPixel(0, 0);
    
                System.Drawing.Bitmap bmpFill = new Bitmap(this.pictureBoxRange.Width, this.pictureBoxRange.Height);
                using (var g = System.Drawing.Graphics.FromImage(bmpFill))
                {
                    //取得した色でビットマップ全体を塗りつぶし
                    using (Brush brush = new SolidBrush(color))
                    {
                        g.FillRectangle(brush, 0, 0, bmpFill.Width, bmpFill.Height);
                    }
                }
                this.pictureBoxFill.Image = bmpFill;
    
                this.label1.Text = $"カーソルの位置={p1.X},{p1.Y}";
                this.label2.Text = $"補正後の位置={p2.X},{p2.Y}";
            }
    
            const int WM_SETTINGCHANGE = 0x1A;
            const nint SPI_SETLOGICALDPIOVERRIDE = 0x9F;
    
            protected override void WndProc(ref Message m)
            {
                if (m.Msg == WM_SETTINGCHANGE && m.WParam == SPI_SETLOGICALDPIOVERRIDE)
                {
                    System.Diagnostics.Debug.WriteLine("設定が変更?");
                }
                base.WndProc(ref m);
            }
    
        
        }
    }
    

    # DPIは拡大率とは別


お客様の回答

回答は、質問作成者が [承諾された回答] としてマークできます。これは、ユーザーが回答が作成者の問題を解決したことを知るのに役立ちます。