แสดงแบบฟอร์มโดยไม่ขโมยโฟกัสหรือไม่


140

ฉันใช้แบบฟอร์มเพื่อแสดงการแจ้งเตือน (ปรากฏที่ด้านล่างขวาของหน้าจอ) แต่เมื่อฉันแสดงแบบฟอร์มนี้จะเป็นการขโมยโฟกัสจากแบบฟอร์มหลัก มีวิธีแสดงแบบฟอร์ม "แจ้งเตือน" นี้โดยไม่ขโมยโฟกัสหรือไม่

คำตอบ:


165

อืมไม่ได้มีเพียงแค่เอาชนะแบบฟอร์มแสดงโดยไม่มีการเปิดใช้งานเพียงพอ

protected override bool ShowWithoutActivation
{
  get { return true; }
}

และหากคุณไม่ต้องการให้ผู้ใช้คลิกที่หน้าต่างการแจ้งเตือนนี้คุณสามารถแทนที่ CreateParams:

protected override CreateParams CreateParams
{
  get
  {
    CreateParams baseParams = base.CreateParams;

    const int WS_EX_NOACTIVATE = 0x08000000;
    const int WS_EX_TOOLWINDOW = 0x00000080;
    baseParams.ExStyle |= ( int )( WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW );

    return baseParams;
  }
}

3
ShowWithoutActivati ​​on, ไม่อยากจะเชื่อว่าฉันไม่พบมันเสียเวลาหนึ่งบ่าย!
Deerchao

2
ฉันต้องตั้งค่าform1.Enabled = falseเพื่อป้องกันการควบคุมภายในจากการขโมยโฟกัส
Jader Dias

23
และปล่อย TopMost ออก
mklein

4
และถ้าคุณไม่ต้องการสูงสุดให้ดูคำตอบอื่น
Roman Starkov

2
ค่าของWS_EX_NOACTIVATEและWS_EX_TOOLWINDOWเป็น0x08000000และ0x00000080ตามลำดับ
Juan

69

ถูกขโมยจากPInvoke.netวิธีการShowWindowของ :

private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;

[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(
     int hWnd,             // Window handle
     int hWndInsertAfter,  // Placement-order handle
     int X,                // Horizontal position
     int Y,                // Vertical position
     int cx,               // Width
     int cy,               // Height
     uint uFlags);         // Window positioning flags

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

static void ShowInactiveTopmost(Form frm)
{
     ShowWindow(frm.Handle, SW_SHOWNOACTIVATE);
     SetWindowPos(frm.Handle.ToInt32(), HWND_TOPMOST,
     frm.Left, frm.Top, frm.Width, frm.Height,
     SWP_NOACTIVATE);
}

(Alex Lyman ตอบคำถามนี้ฉันแค่ขยายมันโดยการวางรหัสโดยตรงคนที่มีสิทธิ์แก้ไขสามารถคัดลอกได้ที่นั่นและลบสิ่งนี้สำหรับสิ่งที่ฉันสนใจ;)


ฉันสงสัยว่าเขาต้องการจริงๆหรือไม่ถ้าแบบฟอร์มที่เขาแสดงที่ด้านล่างซ้ายของหน้าจอของเขามันอยู่ในหัวข้ออื่นหรือไม่?
Patrick Desjardins

50
ฉันพบว่าไม่น่าเชื่อว่าเรายังคงต้องลิงก์ไปยังไฟล์ DLL ภายนอกเพื่อโต้ตอบกับแบบฟอร์ม เราอยู่ที่. NET Framework เวอร์ชัน 4 !! ถึงเวลาที่จะห่อมัน Microsoft
Maltrap

9
คำตอบที่ยอมรับไม่ถูกต้อง ค้นหา ShowWithoutActivation
mklein

เพียงเพิ่มfrm.Hide (); ที่จุดเริ่มต้นของฟังก์ชั่น ShowInactiveTopmost ถ้าคุณต้องการให้แบบฟอร์มของคุณไม่มีการโฟกัสโดยตรง อย่าลืม: ใช้ System.Runtime.InteropServices; เพื่อให้โค้ดนี้ทำงาน
Zitun

1
@Talha รหัสนี้ไม่มีส่วนเกี่ยวข้องกับเหตุการณ์โหลด เหตุการณ์โหลดเกิดขึ้นเมื่อฟอร์มกำลังถูกโหลดไม่ใช่เมื่อกำลังแสดงฟอร์ม
TheSoftwareJedi


12

นี่คือสิ่งที่ได้ผลสำหรับฉัน มันให้ TopMost แต่ไม่มีการขโมยโฟกัส

    protected override bool ShowWithoutActivation
    {
       get { return true; }
    }

    private const int WS_EX_TOPMOST = 0x00000008;
    protected override CreateParams CreateParams
    {
       get
       {
          CreateParams createParams = base.CreateParams;
          createParams.ExStyle |= WS_EX_TOPMOST;
          return createParams;
       }
    }

อย่าลืมตั้งค่า TopMost ในตัวออกแบบ Visual Studio หรือที่อื่น ๆ

สิ่งนี้ถูกขโมยทำผิดยืมไปจากที่นี่ (คลิกที่การแก้ไขปัญหา):

https://connect.microsoft.com/VisualStudio/feedback/details/401311/showwithoutactivation-is-not-supported-with-topmost


1
+ งานที่ไม่มีการโฟกัสมากที่สุดและดูเหมือนว่าจะเป็นคำตอบที่สะอาดที่สุด
feos

สูงสุดถูกคัดค้านตั้งแต่ Windows 8 ที่ Microsoft ลงโทษคุณเมื่อใช้งาน ผลกระทบคือหลังจากเปิดหน้าต่างด้านบนสุดแล้วปิด Windows จะย้ายหน้าต่างอื่นของแอปพลิเคชันของคุณไปยังพื้นหลัง นี่ไม่ใช่พฤติกรรมที่ต้องการสำหรับแอปพลิเคชันของคุณ ไมโครซอฟท์ได้ใช้สิ่งนี้เพราะในอดีตโปรแกรมเมอร์จำนวนมากใช้วิธีการที่ล่วงละเมิดมากที่สุด สูงสุดคือก้าวร้าวมาก ฉันไม่เคยใช้มัน
Elmue

9

การทำเช่นนี้ดูเหมือนว่าจะเป็นการแฮก แต่ดูเหมือนว่าจะทำงาน:

this.TopMost = true;  // as a result the form gets thrown to the front
this.TopMost = false; // but we don't actually want our form to always be on top

แก้ไข: โปรดทราบนี่เป็นเพียงการยกแบบฟอร์มที่สร้างไว้แล้วโดยไม่ขโมยโฟกัส


ดูเหมือนจะไม่ทำงานที่นี่ ... อาจเป็นเพราะเปิด "แบบฟอร์มแจ้งเตือน" นี้ในชุดข้อความอื่น
Matías

1
อาจเป็นไปได้ว่าในกรณีนี้คุณจำเป็นต้องทำการเรียกใช้ InVoke () เพื่อเรียกเมธอดอีกครั้งเป็นเธรดที่ถูกต้อง โดยทั่วไปแล้วการทำงานกับฟอร์มจากเธรดที่ไม่ถูกต้องจะทำให้เกิดข้อยกเว้นขึ้นมา
Matthew Scharley

ขณะนี้ใช้งานได้มันเป็นวิธีการแฮ็กที่กล่าวถึงและทำให้ BSOD สำหรับฉันภายใต้เงื่อนไขบางประการดังนั้นจงระวังสิ่งนี้
Raphael Smit

สูงสุดถูกคัดค้านตั้งแต่ Windows 8 ที่ Microsoft ลงโทษคุณเมื่อใช้งาน ผลกระทบคือหลังจากเปิดหน้าต่างด้านบนสุดแล้วปิดหน้าต่าง Windows จะย้ายหน้าต่างอื่นของแอปพลิเคชันของคุณไปยังพื้นหลัง นี่ไม่ใช่พฤติกรรมที่ต้องการสำหรับแอปพลิเคชันของคุณ ไมโครซอฟท์ได้ใช้สิ่งนี้เพราะในอดีตโปรแกรมเมอร์จำนวนมากใช้วิธีการที่ล่วงละเมิดมากที่สุด สูงสุดคือก้าวร้าวมาก ฉันไม่เคยใช้มัน
Elmue

9

โค้ดตัวอย่างจาก pinvoke.net ในคำตอบของ Alex Lyman / TheSoftwareJedi จะทำให้หน้าต่างเป็นหน้าต่าง "สุดยอด" ซึ่งหมายความว่าคุณไม่สามารถวางไว้ด้านหลังหน้าต่างปกติหลังจากที่มันโผล่ขึ้นมา จากคำอธิบายของ Matias ว่าเขาต้องการใช้สิ่งนี้เพื่ออะไรนั่นอาจเป็นสิ่งที่เขาต้องการ แต่ถ้าคุณต้องการให้ผู้ใช้สามารถวางหน้าต่างไว้ด้านหลังหน้าต่างอื่นหลังจากที่คุณเปิดมันขึ้นมาให้ใช้ HWND_TOP (0) แทน HWND_TOPMOST (-1) ในตัวอย่าง


6

ใน WPF คุณสามารถแก้ไขได้ดังนี้:

ในหน้าต่างใส่คุณสมบัติเหล่านี้:

<Window
    x:Class="myApplication.winNotification"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Notification Popup" Width="300" SizeToContent="Height"
  WindowStyle="None" AllowsTransparency="True" Background="Transparent" ShowInTaskbar="False" Topmost="True" Focusable="False" ShowActivated="False" >
</Window>

แอตทริบิวต์สุดท้ายคือสิ่งที่คุณต้องการ ShowActivated = "เท็จ"


4

ฉันมีสิ่งที่คล้ายกันและฉันเพียงแค่แสดงแบบฟอร์มการแจ้งเตือนแล้วทำ

this.Focus();

เพื่อนำโฟกัสกลับมาที่ฟอร์มหลัก


เรียบง่าย แต่มีประสิทธิภาพ!
Tom

3

สร้างและเริ่มฟอร์มการแจ้งเตือนในเธรดแยกต่างหากและรีเซ็ตโฟกัสกลับไปที่ฟอร์มหลักของคุณหลังจากที่เปิดแบบฟอร์ม มีแบบฟอร์มการแจ้งเตือนให้เหตุการณ์ OnFormOpened ที่ถูกไล่ออกจากForm.Shownเหตุการณ์ บางสิ่งเช่นนี้

private void StartNotfication()
{
  Thread th = new Thread(new ThreadStart(delegate
  {
    NotificationForm frm = new NotificationForm();
    frm.OnFormOpen += NotificationOpened;
    frm.ShowDialog();
  }));
  th.Name = "NotificationForm";
  th.Start();
} 

private void NotificationOpened()
{
   this.Focus(); // Put focus back on the original calling Form
}

นอกจากนี้คุณยังสามารถจัดการกับวัตถุ NotifcationForm ของคุณเพื่อให้สามารถปิดโดยทางโปรแกรมโดยแบบฟอร์มหลัก ( frm.Close())

รายละเอียดบางอย่างหายไป แต่หวังว่าจะทำให้คุณไปในทิศทางที่ถูกต้อง


วิธีนี้จะใช้งานได้หากแบบฟอร์มของคุณเป็นแบบฟอร์มที่ใช้งานอยู่เดิม การทำเช่นนั้นขัดกับวัตถุประสงค์หลักของการแจ้งเตือนประเภทนี้
Alex Lyman

1
ฮะ? นั่นคือจุดประสงค์ของการแจ้งเตือน - เพื่อวางและฟื้นโฟกัสกลับคืนสู่รูปแบบที่ใช้งานอยู่เดิม
Bob Nadler

2
สิ่งนี้จะให้ความสำคัญกับแบบฟอร์มในแอปพลิเคชันของคุณเท่านั้นถ้าหากมีบางโปรแกรมที่ใช้งานอยู่ในเวลานั้น การแสดงหน้าต่างการแจ้งเตือน (โดยปกติแล้วจะให้ผู้ใช้อัปเดตสถานะของแอปพลิเคชันของคุณ) มีประโยชน์จริง ๆ เมื่อพวกเขาไม่ได้ดูแอปพลิเคชันของคุณ
Alex Lyman

3

คุณอาจต้องการพิจารณาประเภทของการแจ้งเตือนที่คุณต้องการแสดง

หากจำเป็นอย่างยิ่งที่จะต้องให้ผู้ใช้ทราบเกี่ยวกับเหตุการณ์บางอย่างการใช้ Messagebox แสดงให้เห็นว่าเป็นวิธีที่แนะนำเนื่องจากมีลักษณะในการบล็อกกิจกรรมอื่น ๆ ในหน้าต่างหลักจนกว่าผู้ใช้จะยืนยัน ระวังการตาบอดแบบผุดขึ้น

หากไม่สำคัญคุณอาจต้องการใช้วิธีอื่นในการแสดงการแจ้งเตือนเช่นแถบเครื่องมือที่ด้านล่างของหน้าต่าง คุณเขียนว่าคุณแสดงการแจ้งเตือนที่ด้านล่างขวาของหน้าจอ - วิธีมาตรฐานในการทำเช่นนี้คือการใช้ปลายบอลลูนกับการรวมกันของไอคอนถาดระบบ


2
- เคล็ดลับบอลลูนไม่ใช่ตัวเลือกเนื่องจากสามารถปิดการใช้งานได้ - แถบสถานะอาจถูกซ่อนหากคุณลดโปรแกรมให้น้อยลงอย่างไรก็ตามขอขอบคุณสำหรับคำแนะนำของคุณ
Matías

3

มันใช้งานได้ดี

ดู: OpenIcon - MSDN และSetForegroundWindow - MSDN

using System.Runtime.InteropServices;

[DllImport("user32.dll")]
static extern bool OpenIcon(IntPtr hWnd);

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

public static void ActivateInstance()
{
    IntPtr hWnd = IntPtr hWnd = Process.GetCurrentProcess().MainWindowHandle;

    // Restore the program.
    bool result = OpenIcon(hWnd); 
    // Activate the application.
    result = SetForegroundWindow(hWnd);

    // End the current instance of the application.
    //System.Environment.Exit(0);    
}

1

คุณสามารถจัดการด้วยตรรกะเพียงอย่างเดียวเช่นกันแม้ว่าฉันต้องยอมรับว่าคำแนะนำข้างต้นที่คุณจบลงด้วยวิธีการ BringToFront โดยไม่ต้องขโมยโฟกัสจริง ๆ เป็นสิ่งที่งดงามที่สุด

อย่างไรก็ตามฉันพบปัญหานี้และแก้ไขโดยใช้คุณสมบัติ DateTime เพื่อไม่อนุญาตให้มีการเรียกใช้ BringToFront อีกต่อไปหากมีการโทรออกไปแล้ว

สมมติคลาสหลัก 'Core' ซึ่งจัดการตัวอย่างเช่นสามฟอร์ม 'Form1, 2 และ 3' แต่ละแบบฟอร์มต้องการคุณสมบัติ DateTime และกิจกรรมเปิดใช้งานที่เรียกคอร์เพื่อให้หน้าต่างอยู่ด้านหน้า:

internal static DateTime LastBringToFrontTime { get; set; }

private void Form1_Activated(object sender, EventArgs e)
{
    var eventTime = DateTime.Now;
    if ((eventTime - LastBringToFrontTime).TotalMilliseconds > 500)
        Core.BringAllToFront(this);
    LastBringToFrontTime = eventTime;
}

จากนั้นสร้างงานใน Core Class:

internal static void BringAllToFront(Form inForm)
{
    Form1.BringToFront();
    Form2.BringToFront();
    Form3.BringToFront();
    inForm.Focus();
}

ในบันทึกย่อด้านข้างหากคุณต้องการคืนค่าหน้าต่างย่อเล็กสุดให้กลับสู่สถานะดั้งเดิม (ไม่ขยายใหญ่สุด) ให้ใช้:

inForm.WindowState = FormWindowState.Normal;

อีกครั้งฉันรู้ว่านี่เป็นเพียงวิธีแก้ไขในการขาด BringToFrontWithoutFocus มันมีความหมายเป็นข้อเสนอแนะถ้าคุณต้องการหลีกเลี่ยงไฟล์ DLL


1

ฉันไม่ทราบว่านี่เป็นการโพสต์แบบ necro หรือไม่ แต่นี่คือสิ่งที่ฉันทำเพราะฉันไม่สามารถทำงานกับวิธี "ShowWindow" และ "SetWindowPos" ของ user32 ได้ และไม่การแทนที่ "ShowWithoutActivati ​​on" จะไม่ทำงานในกรณีนี้เนื่องจากหน้าต่างใหม่ควรอยู่ด้านบนเสมอ อย่างไรก็ตามฉันสร้างเมธอดผู้ช่วยเหลือที่ใช้ฟอร์มเป็นพารามิเตอร์ เมื่อเรียกใช้จะแสดงแบบฟอร์มนำมาไว้ด้านหน้าและทำให้เป็น TopMost โดยไม่ขโมยโฟกัสของหน้าต่างปัจจุบัน (เห็นได้ชัดว่าเป็นเช่นนั้น แต่ผู้ใช้จะไม่สังเกตเห็น)

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

    [DllImport("user32.dll")]
    static extern IntPtr SetForegroundWindow(IntPtr hWnd);

    public static void ShowTopmostNoFocus(Form f)
    {
        IntPtr activeWin = GetForegroundWindow();

        f.Show();
        f.BringToFront();
        f.TopMost = true;

        if (activeWin.ToInt32() > 0)
        {
            SetForegroundWindow(activeWin);
        }
    }

0

ฉันรู้ว่ามันอาจจะฟังดูงี่เง่า แต่สิ่งนี้ได้ผล:

this.TopMost = true;
this.TopMost = false;
this.TopMost = true;
this.SendToBack();

หากคุณส่งหน้าต่างด้านหน้าไปทางด้านหลังหน้าต่างนั้นอาจไม่ปรากฏอีกต่อไปหากหน้าต่างพื้นหลังซ้อนทับหน้าต่างด้านหน้าใหม่
TamusJRoyce

0

ฉันต้องทำสิ่งนี้กับหน้าต่าง TopMost ฉันใช้วิธี PInvoke ด้านบน แต่พบว่าเหตุการณ์การโหลดของฉันไม่ได้รับการเรียกเหมือน Talha ด้านบน ในที่สุดฉันก็ประสบความสำเร็จ บางทีนี่อาจช่วยใครซักคน นี่คือทางออกของฉัน:

        form.Visible = false;
        form.TopMost = false;
        ShowWindow(form.Handle, ShowNoActivate);
        SetWindowPos(form.Handle, HWND_TOPMOST,
            form.Left, form.Top, form.Width, form.Height,
            NoActivate);
        form.Visible = true;    //So that Load event happens

-4

เมื่อคุณสร้างฟอร์มใหม่โดยใช้

Form f = new Form();
f.ShowDialog();

มันขโมยโฟกัสเพราะรหัสของคุณไม่สามารถดำเนินการต่อในรูปแบบหลักจนกว่าจะปิดแบบฟอร์มนี้

ข้อยกเว้นคือการใช้เธรดเพื่อสร้างแบบฟอร์มใหม่แล้ว Form.Show () ตรวจสอบให้แน่ใจว่าเธรดนั้นสามารถมองเห็นได้ทั่วโลกเพราะถ้าคุณประกาศภายในฟังก์ชั่นทันทีที่ฟังก์ชั่นของคุณออกเธรดของคุณจะจบลงและรูปแบบจะหายไป


โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.