8
votes

I'm having some issues getting a notification box to behave correctly in c#. Basically I'm showing a boarderless form in the lower right hand side of the screen, which displays a message for a few seconds and then disappears. The problem is that I need it to appear on top of other windows without it ever being able to steal focus. Ideally, I want it to be purely managed code, although looking through similar examples I doubt this will be possible.

At the moment I'm preventing it from stealing focus when calling Form.Show() with an override:

protected override bool ShowWithoutActivation // stops the window from stealing focus
{
    get { return true; }
}

and then ignoring mouse clicks with:

    private const int WM_MOUSEACTIVATE = 0x0021;
    private const int MA_NOACTIVATEANDEAT = 0x0004;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_MOUSEACTIVATE)
        {
            m.Result = (IntPtr)MA_NOACTIVATEANDEAT;
            return;
        }
        base.WndProc(ref m);
    }

However I find that if I use these in conjunction with TopMost = true (which I need), it gains focus anyway, and if all other windows are minimised, it also gains focus.

So, is there any way to flat out prevent a form from ever gaining focus (whether via mouse click, alt-tab, etc), while still being the top most/second top most form? Even just giving focus immediately back to the window it stole it from would work (although introduce flickering).

Any suggestions would be greatly appreciated, I'm really stuck with this.

EDIT:

Ok, so I finally managed to get this working using:

protected override bool ShowWithoutActivation // stops the window from stealing focus
{
    get { return true; }
}

// and

const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOPMOST = 0x00000008;

protected override CreateParams CreateParams
{
    get
    {
        CreateParams param = base.CreateParams;
        param.ExStyle |= WS_EX_TOPMOST; // make the form topmost
        param.ExStyle |= WS_EX_NOACTIVATE; // prevent the form from being activated
        return param;
    }
}

// and

[DllImport("user32.dll")]
private extern static IntPtr SetActiveWindow(IntPtr handle);
private const int WM_ACTIVATE = 6;
private const int WA_INACTIVE = 0;

private const int WM_MOUSEACTIVATE = 0x0021;
private const int MA_NOACTIVATEANDEAT = 0x0004;

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_MOUSEACTIVATE)
    {
        m.Result = (IntPtr)MA_NOACTIVATEANDEAT; // prevent the form from being clicked and gaining focus
        return;
    }
    if (m.Msg == WM_ACTIVATE) // if a message gets through to activate the form somehow
    {
        if (((int)m.WParam & 0xFFFF) != WA_INACTIVE)
        {

            if (m.LParam != IntPtr.Zero)
            {
                SetActiveWindow(m.LParam);
            }
            else
            {
                // Could not find sender, just in-activate it.
                SetActiveWindow(IntPtr.Zero);
            }

        }
    }

I also added Form.Hide() to the GotFocus event so that even if it does somehow get focus, it simply closes and gets out of the users way asap.

Also, if anyone is wondering, the constants for all the window styles etc. can be found in WINUSER.H, its online at http://www.woodmann.com/fravia/sources/WINUSER.H if you can't find it.

However, if anyone can see a more elegant way of doing this, it would be appreciated.

3

3 Answers

3
votes

Possibly WS_EX_NOACTIVATE extended window style is what you are looking for. Window with this style is not activated when clicked. For example, Virtual Keyboard window has this style.

To apply this style to window, override CreateParams function and change baseParams.ExStyle.

3
votes

In WPF try this:

ShowActivated="False"
0
votes

I'm not looking for points here as the original poster already posted a solution that worked for them but I wanted to share my experience with this problem. Using the solution above (which is at the bottom of the question instead of in answer form) gives me a Win32Exception: Error creating window handle error. when using the WndProc code as it is posted there. The ShowWithoutActivation and CreateParams part works great to prevent activation of a form and still keep it topmost.

I came up with an alternate solution to preventing a form from being clicked using SetWindowLong which makes the form transparent and therefore it can be clicked through and SetLayeredWindowAttributes which sets the transparency back to normal so you can see the form again but still retain the ability to click through the form.

NOTE: You -CANNOT- interact with the form at all in this state and even trying to click the 'X' button will just click whatever is behind the form at that position so you will need to use code to close the form:

public partial class Form1 : Form
{
    private enum GWL : int
    {
        ExStyle = -20
    }

    private enum WS_EX : int
    {
        Transparent = 0x20,
        Layered = 0x80000
    }

    public enum LWA : int
    {
        ColorKey = 0x1,
        Alpha = 0x2
    }

    [DllImport("user32.dll")]
    static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

    [DllImport("user32.dll")]
    static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);

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

    const int WS_EX_NOACTIVATE = 0x08000000;
    const int WS_EX_TOPMOST = 0x00000008;

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams param = base.CreateParams;
            param.ExStyle |= WS_EX_TOPMOST; // make the form topmost
            param.ExStyle |= WS_EX_NOACTIVATE; // prevent the form from being activated
            return param;
        }
    }

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        // Prevent form from showing up in taskbar which also prevents activation by Alt+Tab 

        this.ShowInTaskbar = false;

        // Allow the form to be clicked through so that the message never physically interferes with work being done on the computer 

        SetWindowLong(this.Handle, (int)GWL.ExStyle, (int)WS_EX.Layered | (int)WS_EX.Transparent);

        // Set the opacity of the form

        byte nOpacity = 255;    // 0 = invisible, 255 = solid, anything inbetween will show the form with transparency
        SetLayeredWindowAttributes(this.Handle, 0, nOpacity, (uint)LWA.Alpha);
    }
}

I was also able to get the original approach working by making a small change to the WndProc code above.

NOTE: This also makes the form unclickable but the behavior is different in that you can actually click the min, max and 'X' buttons but nothing happens when you do. The mouse cursor also changes when you are at the edge of the form as if to resize but it doesn't allow resizing:

public partial class Form1 : Form
{
    protected override bool ShowWithoutActivation
    {
        get { return true; }
    }

    const int WS_EX_NOACTIVATE = 0x08000000;
    const int WS_EX_TOPMOST = 0x00000008;

    protected override CreateParams CreateParams
    {
        get
        {
            CreateParams param = base.CreateParams;
            param.ExStyle |= WS_EX_TOPMOST; // make the form topmost
            param.ExStyle |= WS_EX_NOACTIVATE; // prevent the form from being activated
            return param;
        }
    }

    [DllImport("user32.dll")]
    private extern static IntPtr SetActiveWindow(IntPtr handle);
    private const int WM_ACTIVATE = 6;
    private const int WA_INACTIVE = 0;

    private const int WM_MOUSEACTIVATE = 0x0021;
    private const int MA_NOACTIVATEANDEAT = 0x0004;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_MOUSEACTIVATE)
        {
            m.Result = (IntPtr)MA_NOACTIVATEANDEAT; // prevent the form from being clicked and gaining focus
            return;
        }
        if (m.Msg == WM_ACTIVATE) // if a message gets through to activate the form somehow
        {
            if (((int)m.WParam & 0xFFFF) != WA_INACTIVE)
            {

                if (m.LParam != IntPtr.Zero)
                {
                    SetActiveWindow(m.LParam);
                }
                else
                {
                    // Could not find sender, just in-activate it.
                    SetActiveWindow(IntPtr.Zero);
                }

            }
        }
        else
        {
            base.WndProc(ref m);
        }
    }

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        // Prevent form from showing up in taskbar which also prevents activation by Alt+Tab 

        this.ShowInTaskbar = false;
    }
}