4

I'm working in C# with WinForms in a large application with multiple forms.

At several points I have another form coming up as a progress screen. Because I can't freeze my UI thread, I have to start the new form in a new thread. I'm using progressform.ShowDialog() to start the form, but because it's in a new thread, it is possible to Click or Alt + Tab back to the main form. I want to disable this.

My thought is that I can put an EventHandler on the mainForm.GotFocus event and redirect focus to progressForm if it is shown. However, the GotFocus event isn't being triggered when you switch applications or move between the progressForm and mainForm. I'm guessing that it's because some element in mainForm has focus, not the form itself.

If anyone knows of a better way to do this (I'm not committed to the EventHandler approach) or working code for the EventHandler approach, it would solve my problem.

Edit

As per the comment, I tried using the Activated event.

// in InitializeForm()
this.Activated += FocusHandler;

// outside of InitializeForm()
void FocusHandler(object sender, EventArgs e)
{
    if (ProgressForm != null)
    {
        ProgressForm.Focus();
    }
}

But it still allowed me to click back to the main form and click buttons.

Chris
  • 2,619
  • 6
  • 27
  • 34
  • 3
    Using two UI threads is a recipe for _all sorts_ of problems. Don't do that. – SLaks Jun 25 '13 at 19:12
  • Unfortunately, I'm coming in at the end of the project to help wrap it up. The dual UI threads is ingrained in the system now. Taking it out would be more trouble than it's worth. – Chris Jun 25 '13 at 19:15
  • 2
    Have you tried with [Form.Activate](http://msdn.microsoft.com/en-us/library/system.windows.forms.form.activate.aspx) method and [Form.Activated](http://msdn.microsoft.com/en-us/library/system.windows.forms.form.activated.aspx) event? – Steve Jun 25 '13 at 19:20
  • I'm looking into it now. It seems viable. – Chris Jun 25 '13 at 19:23
  • Please look at the edited post. – Chris Jun 25 '13 at 19:34
  • Rather than intercepting focus and immediately blurring (which has an inherently bad user experience) just disable the parent form and all of it's controls. Then the user can't interact with it, but it's clearer (visually) that it's intentional. – Servy Jun 25 '13 at 19:41
  • 1
    The simple way is to just set the form's Enable property to false so that it cannot be activated. Check [this answer](http://stackoverflow.com/a/17217832/17034) for the kind of trouble you can get into by displaying UI on more than one thread. – Hans Passant Jun 25 '13 at 19:41
  • Cant you display the form as modal? Prevents any other form being accessed apart from the form that is displayed. – craig1231 Jun 25 '13 at 19:41
  • @craig1231 The problem is that he has multiple UI threads. – Servy Jun 25 '13 at 19:42
  • @Servy, then don't use multiple UI threads if they are not needed... – craig1231 Jun 25 '13 at 19:43
  • @craig1231 Several people have already made that comment. You should read through the comments already posted... – Servy Jun 25 '13 at 19:44
  • @Chris, does the content need to be refreshed on forms behind the progress form, when the progress form is displayed? – craig1231 Jun 25 '13 at 19:47
  • @craig1231, Not inherently. The progress form needs to be able to be updated. The problem is that the rest of the application does logic and other processing while the progress form is shown. That's why ShowDialog is a problem. It stops all the processing for the thread with the logic. – Chris Jun 25 '13 at 19:51
  • @Chris, so does the application perform these logic tasks on the main thread or on a background dedicated thread? – craig1231 Jun 25 '13 at 19:53
  • @craig1231, The logic is all on the main thread. There are way too many places in the system for me to try to change them all now. – Chris Jun 25 '13 at 19:55
  • @all, I really appreciate the info on keeping all UI on the same thread. In the future I will definitely keep this practice. However, for now I'm in a situation where it's not an option. I don't have the time to change it. I don't need the most elegant solution. I just need one that's going to get the job done. – Chris Jun 25 '13 at 20:00

1 Answers1

0

I've tried some ways and found this which does work as you want, the whole idea is to filter some message from your main UI when your progress form is shown:

public partial class Form1 : Form
{
    [DllImport("user32")]
    private static extern int SetForegroundWindow(IntPtr hwnd);        
    public Form1()
    {
        InitializeComponent();            
    }
    ChildUI child = new ChildUI();
    bool progressShown;
    IntPtr childHandle;
    //I suppose clicking on the button1 on the main ui form will show a progress form.
    private void button1_Click(object sender, EventArgs e)
    {
        if(!progressShown)
           new Thread(() => { progressShown = true; childHandle = child.Handle; child.ShowDialog(); progressShown = false; }).Start();
    }
    protected override void WndProc(ref Message m)
    {
        if (progressShown&&(m.Msg == 0x84||m.Msg == 0xA1||m.Msg == 0xA4||m.Msg == 0xA3||m.Msg == 0x6))  
        //0x84:  WM_NCHITTEST
        //0xA1:  WM_NCLBUTTONDOWN
        //0xA4:  WM_NCRBUTTONDOWN 
        //0xA3   WM_NCLBUTTONDBLCLK   //suppress maximizing ...
        //0x6:   WM_ACTIVATE         //suppress focusing by tab...
        {
            SetForegroundWindow(childHandle);//Bring your progress form to the front
            return;//filter out the messages
        }
        base.WndProc(ref m);
    }
}

if you want to show your progress form normally (not a Dialog), using Application.Run(), showing form normally (using Show()) without processing some while loop will terminate the form almost right after showing it:

private void button1_Click(object sender, EventArgs e)
    {
        //progressShown = true;
        //child.Show();
        if (!progressShown)
        {                
            new Thread(() => {
                progressShown = true;
                if (child == null) child = new ChildUI();
                childHandle = child.Handle;
                Application.Run(child);
                child = null;
                progressShown = false;
            }).Start();
        }
    }

I've tested and it works like a charm.

King King
  • 61,710
  • 16
  • 105
  • 130