0

I have setup a custom control that acts as a loading overlay, the overlay is a form with a picturebox on it to show the image.

When the overlay shows it goes into position and is infront of the main form calling it, this looks great. However when the user moves the form or resizes it the form then goes behind to main form.

The overlay form moves and resizes without problems when the main form is moving or resizing, however after it has finished resizing or moving the form goes behind the main form. How can I bring the overlay form back to the top without using TopMost?

I call the below code from the main form using

LoadingControl p = new LoadingControl(dataGridView1, this);
p.Show();

And the overlay form:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;

public class LoadingControl : Form
{
    private Color BackgroundColour = Color.Black;
    private double BackgroundOpacity = 0.50;
    private Image LoadingImage = APPNAME.Properties.Resources.loading_120x128;
    private Form Mainform;
    private Control MainControl;

    public LoadingControl(Control parent, Form frm)
    {
        MainControl = parent;
        Mainform = frm;
        SetupForm();

        Size = parent.ClientSize;
        Location = parent.PointToScreen(Point.Empty);

        Mainform.Move += AdjustPosition;
        MainControl.SizeChanged += AdjustPosition;
    }
    private void SetupForm()
    {
        FormBorderStyle = FormBorderStyle.None;
        BackColor = BackgroundColour;
        Opacity = BackgroundOpacity;//0.50;
        ShowInTaskbar = false;
        StartPosition = FormStartPosition.Manual;

        PictureBox pbox = new PictureBox {Image = LoadingImage, Parent = this};
        pbox.Width = pbox.Image.Width;
        pbox.Height = pbox.Image.Height;
        pbox.Left = (Width/2) - (pbox.Width/2);
        pbox.Top = (Height/2) - (pbox.Height/2)-10;
        pbox.Anchor = AnchorStyles.None;
        Controls.Add(pbox);
    }

    public void SetLoadingImage(Image img)
    {
        LoadingImage = img;
    }

    public void SetBackgroundColour(Color col)
    {
        BackgroundColour = col;
    }

    public void SetOpacity(double Opa)
    {
        BackgroundOpacity = Opa;
    }

    private void AdjustPosition(object sender, EventArgs e)
    {
        //TopMost = true;
        BringToFront();

        ClientSize = MainControl.ClientSize;
        Location = MainControl.PointToScreen(Point.Empty);
        //Mainform.Activate();      
        //TopMost = false;
        BringToFront();
        Focus();
        //MakeTopMost(this);
    }
}
Neo
  • 2,305
  • 4
  • 36
  • 70
  • 1
    It is a simple mistake, you are using the wrong Show() method. Use the Show(owner) overload so the overlay is an owned window and guaranteed to overlay its owner. Your code needs work so the overlay doesn't prevent moving and sizing, check [this post](http://stackoverflow.com/questions/4503210/draw-semi-transparent-overlay-image-all-over-the-windows-form-having-some-contro/4503371#4503371) for code. – Hans Passant Feb 02 '15 at 09:30

1 Answers1

1

No matter how many times you call BringToFront() it will not move your form on top of the active one. BringToFront() does not make a control a top-level control, and it does not raise the Paint event.

There are too ways to go about fixing it. The best way IMO is to make your loading control a child of the main form instead of them being separate and you having to manually position the control after every move or resize.

The other method that could be done is to change the control window's z-order and activate it after every time a move, resize or maximize/minimize event is raised.

This can be achieved with a little bit of p-invokes:

public void FocusForm()
{
    // force window to have focus
    uint foreThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
    uint appThread = GetCurrentThreadId();
    const uint SW_SHOW = 5;
    if (foreThread != appThread)
    {
        AttachThreadInput(foreThread, appThread, true);
        BringWindowToTop(this.Handle);
        ShowWindow(this.Handle, SW_SHOW);
        AttachThreadInput(foreThread, appThread, false);
    }
    else
    {
        BringWindowToTop(this.Handle);
        ShowWindow(this.Handle, SW_SHOW);
    }
    this.Activate();
}

[DllImport("user32.dll")]
static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr ProcessId);

[DllImport("kernel32.dll")]
static extern uint GetCurrentThreadId();

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

[DllImport("user32.dll")]
static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);

[DllImport("user32.dll", SetLastError = true)]
static extern bool BringWindowToTop(IntPtr hWnd);

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

I use this to set my main window to be top most when started so you might want to lose the Activate() call at the end.

I must say that even though this might work for you [I haven't fully tested it for move and resize], I suggest you revise your code and put the loading control form as a child to your main form.

EDIT:

If you need a transparent control then you can check the accepted answer to this SO question. I cannot post the code here as I do not want to make a false claim that it is my code.

Community
  • 1
  • 1
Zaid Amir
  • 4,727
  • 6
  • 52
  • 101
  • the loading control is a form not a control, when I try to assign the parent as the main form it throws an exception "Top-level control cannot be added to a control." – Neo Feb 01 '15 at 11:12
  • @Neo Well it is true you cannot add it as a control but you can still initialize it from within your main form and use the `Form.Show()` method. Basically what you are doing here is passing the parent to its (supposed) child where it should be the other way around. – Zaid Amir Feb 01 '15 at 11:24
  • sorry not following, I added the call method I use to the above code – Neo Feb 01 '15 at 11:28
  • @Neo long story short, you should revise your code and make your loading form a control. or use the second method in my answer – Zaid Amir Feb 01 '15 at 11:38
  • sadly the code in your post worked until you finish moving the form, then the same issue. The reason it is a form is because of the semi-transparency values, when I extended the panel control the transparency refused to work. I thought of using the GotFocus and LostFocus of the main form as this would cure the issue, but they never fire – Neo Feb 01 '15 at 11:52
  • @Neo try calling FocusForm() after the main form is activated. You can register the form's Activated event and call the method when its raised. – Zaid Amir Feb 01 '15 at 12:24
  • 1
    @Neo as for transparent controls you can check [the answer to this SO question](http://stackoverflow.com/questions/9358500/winforms-making-a-control-transparent) – Zaid Amir Feb 01 '15 at 12:28
  • strange that never showed up in my searches, if you add this onto your post i'll mark it as the accepted answer :) – Neo Feb 01 '15 at 18:41