Non-blocking drag and drop in a Windows Forms app

FleetCommand 21 Reputation points
2022-03-08T00:11:58.673+00:00

Hi. I'm writing a Windows Forms app that supports accepting files via drag-and-drop. Unfortunately, my code seems to disrupt the app from which the dragging started.

Here is my code:

public Form1()
{
    InitializeComponent();
    AllowDrop = true; //In the actual app, I set this in the form designer.
}

private void Form1_DragEnter(object sender, DragEventArgs e)
{
    if ((e.Data == null) || e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;
}

private void Form1_DragDrop(object sender, DragEventArgs e)
{
    if ((e.Data == null) || !e.Data.GetDataPresent(DataFormats.FileDrop)) return;
    string[] FilesToGet = (string[])e.Data.GetData(DataFormats.FileDrop);
    if (!PreliminaryValidation()) { return; }
    StartProcessing(FilesToGet);
}

private bool PreliminaryValidation()
{
    // Replace this with the actual validation code
    return MessageBox.Show(
        "You have not read the manual! Would you like to proceed at your own risk?",
        "Saftey alert",
        MessageBoxButtons.YesNo
    ) switch { DialogResult.Yes => true, DialogResult.No => false };
}

private void StartProcessing(string[] FilesToGet)
{
    this.SuspendLayout();
    listView1.Items.Clear();
    foreach (var item in FilesToGet)
    {
        _ = listView1.Items.Add(item);
    }
    this.ResumeLayout(false);

    // Processing code goes here.
    throw new NotImplementedException();
}

When I run my app and drag a group of files onto it, the following happens:

  1. The originating app (probably File Explorer) becomes unresponsive.
  2. My app saves a list of files in the FilesToGet array.
  3. After a brief check, my app may displays a message box to confirm a potentially risky operation. (For the sake of this question's applicability, the message box always appears.)
  4. My app starts processing the files. It could take up hours, depending on the number, size, and complexity of the files.
  5. The originating app becomes responsive again.

Now the question: How can I make step 5 happen right after step 2?

Developer technologies | Windows Forms
Developer technologies | C#
{count} votes

Accepted answer
  1. RLWA32 49,636 Reputation points
    2022-03-16T09:15:21.753+00:00

    As long as you insist on using a modal message box in the dragdrop method then the originating application will freeze until you dismiss the message box and return from the method.

    See my earlier comment about using a timer.

    For example,

        public partial class Form1 : Form
        {
            string[] FilesToGet;
            bool ready = true;
            public Form1()
            {
                InitializeComponent();
            }
    
            private void Form1_DragDrop(object sender, DragEventArgs e)
            {
                if ((e.Data == null) || !e.Data.GetDataPresent(DataFormats.FileDrop))return;
                FilesToGet = (string[])e.Data.GetData(DataFormats.FileDrop);
                ready = false;
                timer1.Start();
                //if (!PreliminaryValidation()) { return; }
                //StartProcessing(FilesToGet);
            }
    
            private void Form1_DragEnter(object sender, DragEventArgs e)
            {
                if (ready && ((e.Data == null) || e.Data.GetDataPresent(DataFormats.FileDrop)))
                    e.Effect = DragDropEffects.Copy;
                else
                    e.Effect = DragDropEffects.None;
            }
            private bool PreliminaryValidation()
            {
                // Replace this with the actual validation code
                DialogResult result = MessageBox.Show(
                    "You have not read the manual! Would you like to proceed at your own risk?",
                    "Saftey alert",
                    MessageBoxButtons.YesNo
                );
    
                return (result == DialogResult.Yes);
            }
            private void StartProcessing()
            {
                this.SuspendLayout();
                listView1.Items.Clear();
                foreach (var item in FilesToGet)
                {
                    _ = listView1.Items.Add(item);
                }
                this.ResumeLayout(false);
                // Processing code goes here.
                //throw new NotImplementedException();
            }
    
            private void timer1_Tick(object sender, EventArgs e)
            {
                timer1.Stop();
    
                if (!ready)
                {
    
                    if(PreliminaryValidation())
                    {
                        StartProcessing();
                    }
    
                    ready = true;
                }
            }
        }
    
    1 person found this answer helpful.

5 additional answers

Sort by: Most helpful
  1. RLWA32 49,636 Reputation points
    2022-03-08T00:33:38.32+00:00

    You need to return from the dragdrop method before processing the files. Also, don't use a modal message box inside the method.


  2. Castorix31 90,686 Reputation points
    2022-03-08T19:30:40.85+00:00

    The main problem is your MessageBox

    You can replace

    MessageBox.Show("Yo!");
    

    by :

    new System.Threading.Thread(new System.Threading.ThreadStart(delegate { MessageBox.Show("Yo!"); })).Start();
    

  3. Karen Payne MVP 35,586 Reputation points Volunteer Moderator
    2022-03-14T11:27:15.003+00:00

    High level look at using TAP (Task-based Asynchronous Pattern) which supports cancellation and optionally IProgress. When done right your application will be completely responsive along with providing feedback to the user. Many will settle for a BackGroundWorker component which in many cases is fine but using TAP pattern provides more options than a BackGroundWorker and is available in not just desktop solutions but any device e.g. mobile and web.

    To get an idea about TAP, check out the code in this class project which has examples for TAP and events which keeps the caller responsive. Also check out the following GitHub repository for other code samples.

    Take time to learn asynchronous programming and your app will be responsive and not mess with other windows apps.


  4. Jack J Jun 25,296 Reputation points
    2022-03-15T08:34:55.207+00:00

    @FleetCommand , Have you considered putting step3 and step4 into one thread? Then you will not face the blocking problem.

    I make a code example and based on my test, there is no blocking indeed.

     private void Form1_DragEnter(object sender, DragEventArgs e)  
            {  
                if ((e.Data == null) || e.Data.GetDataPresent(DataFormats.FileDrop)) e.Effect = DragDropEffects.Copy;  
            }  
            private void Form1_DragDrop(object sender, DragEventArgs e)  
            {  
                 
                if ((e.Data == null) || !e.Data.GetDataPresent(DataFormats.FileDrop)) return;  
                string[] FilesToGet = (string[])e.Data.GetData(DataFormats.FileDrop);  
      
                Task.Run(() =>  
                {  
                    MessageBox.Show("Yo!");  
                    Form1.StartProcessing(FilesToGet);  
      
                });  
                 
      
      
            }  
            private static void StartProcessing(string[] filesToGet)  
            {  
      
                for (int i = 0; i < 100000000; i++)  
                {  
                    Console.WriteLine(i);  
                }  
            }  
      
            private void button1_Click(object sender, EventArgs e)  
            {  
                MessageBox.Show("Test");  
            }  
    

    However, If you insist on using Messagebox in the method, Inevitably, you need to manually click on the messagebox so that the program will not freeze.

    I used 100000000 loop and button to check if the app is responsive.

    As the following result shown, it will not block the app when we output the number in another thread.

    183221-10.gif

    Best Regards,
    Jack


    If the answer is the right solution, please click "Accept Answer" and upvote it.If you have extra questions about this answer, please click "Comment".

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.