c# - باستخدام SetWindowPos مع شاشات متعددة



windows user32 (1)

باستخدام user32.dll و C # كتبت الطريقة التي تراها أدناه. باستخدام مقبض العملية للإطار ، فإنه سيتم تعيين موضع الإطار في موقع {x,y} مقدم.

ومع ذلك ، في بيئة متعددة المراقبة ، يقوم الكود أدناه بتعيين موضع النافذة على الشاشة الأساسية فقط. أود أن أكون قادرًا على تحديد جهاز العرض أيضًا.
هل يمكن لشخص ما أن يوضح كيف يمكن تحقيق ذلك باستخدام SetWindowPos أو ربما مع وظيفة user32.dll أخرى؟

[DllImport("user32.dll", SetLastError = true)]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

private const int SWP_NOSIZE = 0x0001;
private const int SWP_NOZORDER = 0x0004;
private const int SWP_SHOWWINDOW = 0x0040;

public static void SetWindowPosition(Process p, int x, int y)
{
    IntPtr handle = p.MainWindowHandle;
    if (handle != IntPtr.Zero)
    {
        SetWindowPos(handle, IntPtr.Zero, x, y, 0, 0, SWP_NOZORDER | SWP_NOSIZE | SWP_SHOWWINDOW);
    }
}

الحل بناء على تعليق جيمي.

هنا هو تكوين جهاز العرض الخاص بي:

لاحظ أن لدي شاشة ثانوية على يسار شاشتي الأساسية. بعد قراءة رابط Virtual Monitor الذي قدمه Jimi ، اكتشفت أنه لنقل النوافذ إلى جهاز العرض الثانوي ، يجب أن استخدم قيمة x سالبة لأنها تركت من أصل جهاز العرض الأساسي (أعلى الزاوية اليسرى ، أو <0،0>).

لذلك ، إذا كنت ترغب في تعيين موضع نافذتي على إحداثيات <0،0> من جهاز العرض الثانوي ، يجب أن أحذف عرض x من الشاشة الثانوية من أصل جهاز العرض الأساسي ، مثل هذا:

<0،0> - <1920،0> = <-1920،0>

الآن ، عندما أتصل بـ SetWindowPosition في رمز العميل الخاص بي ، أسميها هكذا:

SetWindowPosition(Process p, -1920, 0);

ملاحظة: لا أعرف ماذا ستفعل إذا كانت الشاشات لها قرارات مختلفة. هذا موضوع أكثر تعقيدًا وليس السؤال الذي أطرحه. أيضًا ، لم أر حاجة لاستكشاف الموضوع بشكل أعمق حيث أن المثال البسيط أعلاه حل جميع مشاكلي.


يعرض نظام التخلص و VirtualScreen

في نظام Windows ، تكون الشاشة الأساسية (منظور البرمجة) هي جهاز العرض الذي يحتوي على موضع الزاوية العليا اليسرى عند Point(0,0) .

هذا يعني أن شاشة العرض الموضوعة على يسار الشاشة الأساسية ، سيكون لها إحداثيات سالب سالبة (إحداثي Y يمكن أن يكون سالبًا إذا كان العرض في تخطيط صورة)
سيكون للعروض على اليمين إحداثيات X موجبة (إحداثي Y يمكن أن يكون سالبًا إذا كان العرض في تخطيط صورة).

يعرض على يسار الشاشة الأساسية :
بمعنى آخر ، يعرض أن يكون له أصل Point.X سالب
أصل Point.X هو مجموع كل Screens[].Width السابقة Screens[].Width ، مطروح من Point.X الأصل Point.X من الشاشة الأساسية.

يعرض على يمين الشاشة الأساسية :
بمعنى آخر ، يعرض أن يكون له أصل موجب لـ Point.X
أصل Point.X هو مجموع كل Screens[].Width السابقة Screens[].Width ، الأساسي مدرج ، يضاف إلى الأصل Point.X إحداثيات الشاشة الأساسية.

ملاحظة مهمة :
إذا لم يكن التطبيق DPIAware ، يمكن اختراق جميع هذه التدابير عن طريق الافتراضية وتوسيع نطاق DPI التلقائي الذي يؤديه النظام. سيتم إضفاء الطابع الرسمي على جميع التدابير إلى 96 نقطة في البوصة الافتراضية: سيتلقى التطبيقون قيمًا محجوبة. يتضمن هذا أيضًا القيم التي تم استردادها من وظائف غير DpiAware Win32 Api. نرى:

ارتفاع تطوير DPI تطبيق سطح المكتب على ويندوز

تمكين الدعم لجميع الأنظمة المستهدفة في ملف app.manifest ، مما app.manifest إلى app.manifest تنفيذ الأقسام المطلوبة.

أضف / قم بإلغاء قسمتي DpiAware و DpiAwareness في ملف app.manifest .
يمكن تعيين وضع PerMonitorV2 Dpi Awareness في ملف app.config (متاح من Windows 10 Creators Edition).

مثال:
النظر في نظام مع 3 شاشات:

PrimaryScreen             (\\.\DISPLAY1):  Width: (1920 x 1080)
Secondary Display (Right) (\\.\DISPLAY2):  Width: (1360 x 768)
Secondary Display (Left)  (\\.\DISPLAY3):  Width: (1680 x 1050)

PrimaryScreen: 
     Bounds: (0, 0, 1920, 1080)      Left: 0      Right: 1920  Top: 0  Bottom: 1080
Secondary Display (Right): 
     Bounds: (1360, 0, 1360, 768)    Left: 1360   Right: 2720  Top: 0  Bottom: 768
Secondary Display (Left): 
     Bounds: (-1680, 0, 1680, 1050)  Left: -1680  Right: 0     Top: 0  Bottom: 1050



إذا قمنا بتغيير ، باستخدام التطبيق الصغير للنظام ، مرجع الشاشة الأساسية ، إعداده على \\.\DISPLAY3 ، سيتم تعديل الإحداثيات وفقًا:


شاشة افتراضية

الشاشة الافتراضية هي شاشة افتراضية تمثل الأبعاد:
الأصل : إحداثي أصل Screen أقصى اليسار
العرض : مجموع كل Screens .
الارتفاع : ارتفاع Screen الأعلى

يتم الإبلاغ عن هذه التدبير بواسطة SystemInformation.VirtualScreen
تم الإبلاغ عن Size الشاشة الأساسي بواسطة SystemInformation.PrimaryMonitorSize
يمكن أيضًا استرداد كافة مقاييس الشاشات الحالية Screen.AllScreens باستخدام Screen.AllScreens وفحص كل خصائص \\.\DISPLAY[N] .

باستخدام المثال السابق كمرجع ، في التصرف الأول ، حدود VirtualScreen هي:

Bounds: (-1680, 0, 3280, 1080)  Left: -1680  Right: 3280   Top: 0  Bottom: 1080

في التصرف الثاني ، حدود VirtualScreen هي:

Bounds: (0, 0, 4960, 1080)  Left: 0  Right: 4960   Top: 0  Bottom: 1080


موضع النافذة داخل منطقة العرض :

تقدم فئة الشاشة طرقًا متعددة يمكن استخدامها لتحديد الشاشة التي يتم فيها عرض نافذة محددة حاليًا:

Screen.FromControl([Control reference])
إرجاع كائن Screen الذي يحتوي على أكبر قسم من مرجع Control المحدد.

Screen.FromHandle([Window Handle])
إرجاع كائن Screen الذي يحتوي على أكبر قسم من Window \ Control المشار إليه بواسطة مؤشر

Screen.FromPoint([Point])
إرجاع كائن Screen الذي يحتوي على Point محددة

Screen.FromRectangle([Rectangle])
إرجاع كائن Screen الذي يحتوي على أكبر قسم من Rectangle المحدد

Screen.GetBounds() (overloaded)
إرجاع بنية Rectangle تشير إلى حدود الشاشة التي تحتوي على:
- Point محددة
- أكبر قسم من Rectangle المحدد
- مرجع Control

لتحديد \\.\DISPLAY[N] الذي يظهر فيه النموذج الحالي ، اتصل (على سبيل المثال):

Screen.FromHandle(this);

لتحديد أي شاشة يتم عرض نموذج ثانوي:
(استخدام العينة يعرض في المثال)

form2 = new Form2();
form2.Location = new Point(-1400, 100);
form2.Show();
Rectangle screenSize = Screen.GetBounds(form2);
Screen screen = Screen.FromHandle(form2.Handle);

screenSize سيكون = إلى \\.\DISPLAY3 حدود.
ستكون screen هي كائن Screen يمثل خصائص \\.\DISPLAY3 .

سيقوم كائن screen أيضًا بالإبلاغ عن \\.\DISPLAY[N] اسم Screen الذي يظهر به النموذج form2 .

الحصول على hMonitor لكائن الشاشة :

يُظهر مصدر مرجع .NET أن hMonitor يتم إرجاع استدعاء [Screen].GetHashCode();

IntPtr monitorHwnd = new IntPtr([Screen].GetHashCode());

أو باستخدام نفس وظائف Win32 الأصلية:

MonitorFromWindow و MonitorFromPoint و MonitorFromRect

[Flags]
internal enum MONITOR_DEFAULTTO
{
    NULL = 0x00000000,
    PRIMARY = 0x00000001,
    NEAREST = 0x00000002,
}

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR_DEFAULTTO dwFlags);

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromPoint([In] POINT pt, MONITOR_DEFAULTTO dwFlags);

[DllImport("User32.dll", SetLastError = true)]
internal static extern IntPtr MonitorFromRect([In] ref RECT lprc, MONITOR_DEFAULTTO dwFlags);

الحصول على مؤشر سياق الجهاز من الشاشة :
طريقة عامة لاسترداد hDC لأي عرض متاح.

يمكن تحديد إحداثيات الشاشة أو جهاز الشاشة باستخدام إحدى الطرق الموضحة مسبقًا عندما يكون هناك حاجة إلى مرجع شاشة معين فقط.

يمكن استخدام الخاصية lpszDriver كمعلمة lpszDriver GDI + CreateDC . سيعود hDC من الشاشة التي يمكن أن تستخدمها Graphics.FromHdc لإنشاء كائن رسومات صالح يسمح بالطلاء على شاشة محددة.

هنا ، بافتراض توفر عرضين على الأقل:

[DllImport("gdi32.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);

[DllImport("gdi32.dll", SetLastError = true, EntryPoint = "DeleteDC")]
internal static extern bool DeleteDC([In] IntPtr hdc);  

public static IntPtr CreateDCFromDeviceName(string deviceName)
{
    return CreateDC(deviceName, null, null, IntPtr.Zero);
}


Screen[] screens = Screen.AllScreens;
IntPtr screenDC1 = CreateDCFromDeviceName(screens[0].DeviceName);
IntPtr screenDC2 = CreateDCFromDeviceName(screens[1].DeviceName);
using (Graphics g1 = Graphics.FromHdc(screenDC1))
using (Graphics g2 = Graphics.FromHdc(screenDC2))
using (Pen pen = new Pen(Color.Red, 10))
{
    g1.DrawRectangle(pen, new Rectangle(new Point(100, 100), new Size(200, 200)));
    g2.DrawRectangle(pen, new Rectangle(new Point(100, 100), new Size(200, 200)));
}

DeleteDC(screenDC1);
DeleteDC(screenDC2);




user32