-2

In a multithreading environment.

I'm using ShowDialog in a progress form to block all user activities on other forms.
After finish part of the process I hide the progress form, I do some tasks and I show (ShowDialog) it again.
Everything's works fine but the progress form flickers.
Is there a way to continue from a ShowDialog without hiding the window or, probably better, is there a way to transform a Show to a ShowDialog and back again?

EDIT
My code is similar to this

class frmProgress : Form
{
    // Standard stuff


    public void DoSomeWorks()
    {
        async Task.Run(() => RunWork1());
        ShowDialog();
        if (_iLikeTheResultOfWork1)
        {
            async Task.Run(() => RunWork2());
            ShowDialog();
        }
    }

    void RunWork1()
    {
        // Do a lot of things including update UI

        Hide();
    }

    void RunWork2()
    {
        // Do a lot of things including update UI

        Hide();
    }
}

EDIT 2
Thanks to everyone for the answers and the suggestions.
At the end I adopted the solution to Group some small works in one bigger work that I use in UI while in unit tests I still test the small works.
Actually it was not the solution I was looking for, I hoped to find a solution based on Handling the message pump or doing something similar (see System.Windows.Forms.Application source code starting from RunDialog(.) and all the called methods where I got lost before posting this question).

bubi
  • 6,414
  • 3
  • 28
  • 45
  • Why do you hide progress form "After finish part of the process"? – Dennis Feb 05 '18 at 07:04
  • Because after the first background thread has finish, the controlling process needs to make some short tasks and eventually run another long task using the same progress form (the progress form contains messages not only a progress bar). – bubi Feb 05 '18 at 07:10
  • 2
    Difficult to help w/o any reproducing code. It heavily depends on how you really do things. – Simon Mourier Feb 08 '18 at 08:22
  • @SimonMourier, I updated the answer – bubi Feb 08 '18 at 10:11
  • If those tasks should run in parallel, you can disable the form before running them and then after finishing all tasks, enable the form again. Otherwise, for me, It's not clear what you are trying to do. – Reza Aghaei Feb 08 '18 at 10:54
  • @RezaAghaei the form has a cancel button. – bubi Feb 08 '18 at 11:27
  • By each question you are revealing a new part of the problem! – Reza Aghaei Feb 08 '18 at 11:28
  • Should they run in parallel or one by one? Are you going to cancel all operations when cancel button pressed? – Reza Aghaei Feb 08 '18 at 11:30
  • @RezaAghaei 2 tasks in parallel, UI + Work. BTW I'm not looking for a workaround on tasks. The example is a prototype, the tasks implements the whole RunAsync pattern (with cancel, progress, ...). The UI already implements all the patterns for proper work (Invoke, BeginInvoke, ...). Everything is working. Now I have 2 options, change the model I'm handling the tasks (it's what you are suggesting but I should test everything again because I haven't the unit tests for the UI) or find a way to continue from a ShowDialog (or an alternative to ShowDialog). – bubi Feb 08 '18 at 11:45
  • 1
    `ShowDialog` is blocking. Forget about show dialog. It doesn't make sense. In fact I cannot imagine what's the usage of `ShowDialog` while you can disable controls of the form (or the whole form) and show a form. – Reza Aghaei Feb 08 '18 at 11:48
  • 1
    A great example of https://meta.stackexchange.com/questions/66377/what-is-the-xy-problem – MineR Feb 09 '18 at 13:10
  • 1
    Is this what you want to do? If so I'l post as C# code: https://stackoverflow.com/questions/13206926/vb-net-progressbar-backgroundworker/13486676 – Jeremy Thompson Feb 12 '18 at 04:17
  • @JeremyThompson thanks for the suggestion. Actually a thread can call Hide() directly (and exit from a ShowDialog) so it's not a problem. The progress bar works because the UI thread is not used to make work. – bubi Feb 12 '18 at 06:13

6 Answers6

4

I suppose you have some methods like following:

void Task1()
{
    Debug.WriteLine("Task1 Started");
    System.Threading.Thread.Sleep(5000);
    Debug.WriteLine("Task1 Finished");
}
void Task2()
{
    Debug.WriteLine("Task2 Started");
    System.Threading.Thread.Sleep(3000);
    Debug.WriteLine("Task2 Finished");
}

If you are going to run them in parallel:

private async void button1_Click(object sender, EventArgs e)
{
    this.Enabled = false;
    //Show a loading image
    await Task.WhenAll(new Task[] {
        Task.Run(()=>Task1()),
        Task.Run(()=>Task2()),
    });
    //Hide the loading image
    this.Enabled = true;
}

If you are going to run them one by one:

private async void button1_Click(object sender, EventArgs e)
{
    this.Enabled = false;
    //Show a loading image
    await Task.Run(()=>Task1());
    await Task.Run(()=>Task2());
    //Hide the loading image
    this.Enabled = true;
}
Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • For this tasks I'd like only to respond to UI events. They can't be run in parallel. I need also to lock all other windows around so I need a modal form. – bubi Feb 08 '18 at 11:31
4

You can't do that. At least there is not an easy way that I am aware of. One way to tackle that is to change your code as follows:

//C#-ish pseudocode based on OPs example; doesn't compile
class frmProgress : Form
{
    // Standard stuff


    public void DoSomeWorks()
    {
        async Task.Run(() => RunWork1());
        ShowDialog();
    }

    void RunWork1()
    {
        // Do a lot of things including update UI

        if (_iLikeTheResultOfWork1)
        {
            async Task.Run(() => RunWork2());
        }
        else
        {
            Hide();
        }
    }

    void RunWork2()
    {
        // Do a lot of things including update UI

        Hide();
    }
}

EDIT For those complaining the code doesn't compile, they are right. But this is the best the OP will get with that code sample and that's the reason I guess he is mercilessly downvoted.

But to make the answer more relevant to others, my suggestion is don't hide the progress form between the the 2 tasks, hide it when you are sure that the tasks have ended. All these by respecting the threading model of the context you are working on, something the OP's code doesn't do. Manipulating the UI in Winforms from methods that will eventually run in other threads won't work.

Stelios Adamantidis
  • 1,866
  • 21
  • 36
2

Referencing Async/Await - Best Practices in Asynchronous Programming

I advise you take advantage of async event handlers and tasks to allow for non blocking calls while the form is in modal via show dialog

class frmProgress : Windows.Form {
    // Standard stuff

    public void DoSomeWorks() {
        Work -= OnWork;
        Work += OnWork;

        Work(this, EventArgs.Empty);//raise the event and do work on other thread   
        ShowDialog();
    }

    private CancellationTokenSource cancelSource;
    private event EventHandler Work = delegate { };

    private async void OnWork(object sender, EventArgs e) {
        Work -= OnWork; //unsubscribe

        cancelSource = new CancellationTokenSource(); //to allow cancellation.

        var _iLikeTheResultOfWork1 = await RunWork1Async(cancelSource.Token);
        if (_iLikeTheResultOfWork1) {
            await RunWork2Async(cancelSource.Token);
        }

        DialogResult = DialogResult.OK; //just an example
    }


    Task<bool> RunWork1Async(CancellationToken cancelToken) {
        // Do a lot of things including update UI

        //if while working cancel called
        if (cancelToken.IsCancellationRequested) {
            return Task.FromResult(false);
        }

        //Do more things

        //return true if successful
        return Task.FromResult(true);
    }

    Task<bool> RunWork2Async(CancellationToken cancelToken) {
        // Do a lot of things including update UI

        //if while working cancel called
        if (cancelToken.IsCancellationRequested) {
            return Task.FromResult(false);
        }

        //Do more things

        //return true if successful
        return Task.FromResult(true);
    }
}

Note the use of cancellation token to allow tasks to be cancelled as needed.

The UI is now unblocked and the async functions can continue the work. No flickering as the form remain shown without any interruptions.

Nkosi
  • 235,767
  • 35
  • 427
  • 472
2

I could be missunderstood, here what I did, handleDialog with wndProc. I created an extra form and used Show(); method. After I showed form, async tasks are started. After I showed the form, I handled that form by wndProc.

protected override void WndProc(ref Message m) {
            if((f2.IsDisposed || !f2.Visible)) {
                foreach(var control in controlList) {
                    control.Enabled = true; // enable all controls or other logic
                }
            }
            if(m.Msg == 0x18 && !f2.IsDisposed) { // notify dialog opens and double checks dialog's situation
                foreach(var control in controlList.Where(ctrl => ctrl.Name != "button1")) {
                    control.Enabled = false; // disable except cancel button
                }
            }
            base.WndProc(ref m);
        }

Hope helps,

Berkay Yaylacı
  • 4,383
  • 2
  • 20
  • 37
2

From the code you posted, you are calling Hide() at the end of your RunWork() methods, and then ShowDialog() right afterwards. If I understand correctly, you want to call Hide() first inside your RunWork() methods, which makes the main UI window accessable while the UI updates are occurring. After everything finishes, then the ShowDialog() method occurs and blocks the main UI thread again.

class frmProgress : Form
{
    public bool _iLikeTheResultOfWork1 = true;

    // Note: changed to async method and now awaiting the task
    public async void DoSomeWorks()
    {
        await Task.Run(() => RunWork1());
        ShowDialog();
        if (_iLikeTheResultOfWork1)
        {
            await Task.Run(() => RunWork2());
            ShowDialog();
        }
    }

    // Hide window and wait 5 seconds. Note that the main window is active during this 5 seconds.
    // ShowDialog() is called again right after this, so the dialog becomes modal again.
    void RunWork1()
    {
        Hide();
        Task.Delay(5000).Wait();

        // Do a lot of things including update UI
    }

    void RunWork2()
    {
        Hide();
        Task.Delay(5000).Wait();

        // Do a lot of things including update UI
    }
}

My code above will call RunWork1(), which hides the dialog for 5 seconds. During this time the main UI window will be active. Afterwards, the method returns and ShowDialog() is called. Close that window to call RunWork2() and start the process over again.

Is this what you are attempting to achieve? I just tested this code and it works with no visual "flickers". If this is what you want, does the "flickering" occur with this method?

Tallissan
  • 46
  • 1
  • 2
1

"the form will always be closed when it leaves its modal message loop, the one that was started by ShowDialog().  The best you can do is simulate modality, display the form with Show() and disable all other windows."

https://social.msdn.microsoft.com/Forums/sharepoint/en-US/3f6c57a1-92fd-49c5-9f46-9454df80788c/possible-to-change-modality-of-a-dialog-box-at-runtime?forum=winforms

So you could enumerate all your app's Windows and enable/disable them when needed, plus set your dialog window to be always on top when simulating modality. I think always on top means it comes above all windows in the OS though, so you may need a timer instead to bring your window to the front (among your app windows) if it is set to simulate modality

George Birbilis
  • 2,782
  • 2
  • 33
  • 35
  • I'm probably looking for a way to set a modal message loop without using ShowDialog and go back to a non modal message loop without hiding the window (that is probably the only way to exit from a ShowDialog). – bubi Feb 08 '18 at 08:33
  • Have you thought of stacking a non modal replica of the modal window under it and sync their movement (or make it non movable) to avoid the flickering? – George Birbilis Feb 08 '18 at 09:10
  • I did not. I prefere to change the model as suggested above. Actually still looking for a way to change the message loop without ShowDialog. I started from Application.RunDialog (private but callable with reflection) then I got lost. – bubi Feb 09 '18 at 15:14