Подтвердить что ты не робот

Создайте новый поток, чтобы открыть новое окно и закрыть его из другого потока

Прямо сейчас у меня есть код С#, чтобы создать новое окно в другом потоке, это работает, но как только откроется новое порожденное окно, оно закрывается и поток заканчивается. Как я могу это сделать, чтобы новое порожденное окно можно было закрыть из первого потока?

Вот "дерево" того, как работает икру:

Основная тема
- Использует функцию в основном потоке, чтобы запустить другую функцию в отдельном потоке, чтобы открыть w-окно, заставляя окно использовать этот поток.

В принципе, я просто хочу, чтобы у каждого из двух окон был свой поток. И иметь возможность управлять порожденным вторичным окном из первого потока окна.

4b9b3361

Ответ 1

Это просто быстрый пример. Это немного более надежный, чем первый, который я написал. Он устраняет существующее состояние гонки, используя p/invoke.

Обновлено. Все еще было состояние гонки. Это должно быть идеально.

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

class MainUIThreadForm : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainUIThreadForm());
    }

    private IntPtr secondThreadFormHandle;

    public MainUIThreadForm()
    {
        Text = "First UI";
        Button button;
        Controls.Add(button = new Button { Name = "Start", Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) });
        button.Click += (s, e) =>
        {
            if (secondThreadFormHandle == IntPtr.Zero)
            {
                Form form = new Form
                {
                    Text = "Second UI",
                    Location = new Point(Right, Top),
                    StartPosition = FormStartPosition.Manual,
                };
                form.HandleCreated += SecondFormHandleCreated;
                form.HandleDestroyed += SecondFormHandleDestroyed;
                form.RunInNewThread(false);
            }
        };
        Controls.Add(button = new Button { Name = "Stop", Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40), Enabled = false });
        button.Click += (s, e) =>
        {
            if (secondThreadFormHandle != IntPtr.Zero)
                PostMessage(secondThreadFormHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
        };
    }

    void EnableStopButton(bool enabled)
    {
        if (InvokeRequired)
            BeginInvoke((Action)(() => EnableStopButton(enabled)));
        else
        {
            Control stopButton = Controls["Stop"];
            if (stopButton != null)
                stopButton.Enabled = enabled;
        }
    }

    void SecondFormHandleCreated(object sender, EventArgs e)
    {
        Control second = sender as Control;
        secondThreadFormHandle = second.Handle;
        second.HandleCreated -= SecondFormHandleCreated;
        EnableStopButton(true);
    }

    void SecondFormHandleDestroyed(object sender, EventArgs e)
    {
        Control second = sender as Control;
        secondThreadFormHandle = IntPtr.Zero;
        second.HandleDestroyed -= SecondFormHandleDestroyed;
        EnableStopButton(false);
    }

    const int WM_CLOSE = 0x0010;
    [DllImport("User32.dll")]
    extern static IntPtr PostMessage(IntPtr hWnd, int message, IntPtr wParam, IntPtr lParam);
}

internal static class FormExtensions
{
    private static void ApplicationRunProc(object state)
    {
        Application.Run(state as Form);
    }

    public static void RunInNewThread(this Form form, bool isBackground)
    {
        if (form == null)
            throw new ArgumentNullException("form");
        if (form.IsHandleCreated)
            throw new InvalidOperationException("Form is already running.");
        Thread thread = new Thread(ApplicationRunProc);
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = isBackground;
        thread.Start(form);
    }
}

Вот первый пример для потомков:

using System;
using System.Drawing;
using System.Threading;
using System.Windows.Forms;

class MainUIThreadForm : Form
{
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new MainUIThreadForm());
    }

    SecondUIThreadForm secondThreadForm;
    public MainUIThreadForm()
    {
        Text = "First UI";
        Button button;
        Controls.Add(button = new Button { Text = "Start second UI thread", AutoSize = true, Location = new Point(10, 10) });
        button.Click += (s, e) =>
            {
                if (secondThreadForm == null || !secondThreadForm.IsHandleCreated)
                    secondThreadForm = SecondUIThreadForm.Create();
            };
        Controls.Add(button = new Button { Text = "Stop second UI thread", AutoSize = true, Location = new Point(10, 40) });
        button.Click += (s, e) =>
        {
            if (secondThreadForm != null && secondThreadForm.IsHandleCreated)
                secondThreadForm.Invoke((Action)(() => secondThreadForm.Close()));
        };
    }
}

class SecondUIThreadForm : Form
{
    static void Main2(object state)
    {
        Application.Run((Form)state);
    }

    public static SecondUIThreadForm Create()
    {
        SecondUIThreadForm form = new SecondUIThreadForm();
        Thread thread = new Thread(Main2);
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start(form);
        return form;
    }

    public SecondUIThreadForm()
    {
        Text = "Second UI";
    }
}

Ответ 2

Я уверен, что вы делаете что-то вроде этого:

new Thread(() => new TestForm().Show()).Start();

потому что это заставляет окно сразу исчезать, как вы описываете.

Попробуйте это вместо:

 new Thread(() => new TestForm().ShowDialog()).Start();

ShowDialog запускает свой собственный насос сообщений и возвращается только после закрытия окна.

Ответ 3

Вы можете сделать это следующим образом:

В Program.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Threading;

namespace TwoWindows
{
    static class Program
    {
        public static Form1 form1;
        public static Form2 form2;
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false); 

            form1 = new Form1();
            form2 = new Form2();

            form1.Form2Property = form2;
            form2.Form1Property = form1;

            form1.Show();
            form2.Show();

            Application.Run();
        }
    }
}

В Form1.cs:

namespace TwoWindows
{
    public partial class Form1 : Form
    {
        public Form2 Form2Property { get; set; }

        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Form2Property.IsDisposed)
                Application.Exit();
        }
    }
}

И Form2.cs:

namespace TwoWindows
{
    public partial class Form2 : Form
    {
        public Form1 Form1Property { get; set; }

        public Form2()
        {
            InitializeComponent();
        }

        protected override void OnClosed(EventArgs e)
        {
            if (Form1Property.IsDisposed)
                Application.Exit();
        }
    }
}

Таким образом вы можете получить две формы в одном и том же потоке и использовать один для управления другим. Если вам нужно использовать потоки, я бы предложил использовать выделенные потоки, которые являются частью классов, а не порождать метод, который можно вызвать более одного раза. Затем используйте ManualResetEvent или AutoResetEvent для управления обработкой потоков. Мне очень нравится подход к использованию чего-то подобного, потому что он безопасен и не тратит много ресурсов на инициализацию потоков.

public class MyClassOrForm
{
    Thread myProcessingThread;
    public AutoResetEvent startProcessing = new AutoResetEvent(false);
    public AutoResetEvent processingFinished = new AutoResetEvent(false);
    public AutoResetEvent killProcessingThread = new AutoResetEvent(false);

    public MyClassOrForm()
    {
        myProcessingThread = new Thread(MyProcess);
    }

    private void MyProcess()
    {
        while (true)
        {
            if (startProcessing.WaitOne())
            {
                // Do processing here

                processingFinished.Set();
            }

            if (killProcessingThread.WaitOne(0))
                return;
        }
    }
}

Затем, как только вы установите данные для обработки, вызовите другой класс или метод

MyClassOrMethodInstance.startProcessing.Set();

И если вам нужно дождаться завершения этой обработки, вставьте:

MyClassOrMethodInstance.processingFinished.WaitOne(time_out_ms);

Это эквивалентно вызову Thread.Join(), но вам не нужно выделять другой поток каждый раз с рисками, которые связаны с потоками, если они зависят от локальных данных или недоделанных дочерних потоков.

Ответ 4

Как вы создали новое окно из второго потока? И что делает поток после создания окна?

Не видя кода, я бы предположил, что проблема в том, что ваш второй поток не передает сообщения в очередь сообщений Windows.

Вы звоните Application.Run в свой второй поток?

Кстати: обратите внимание, что ваш дизайн имеет некоторые ограничения. Первый поток не сможет напрямую управлять вторым окном. Всякий раз, когда вы пытаетесь манипулировать любым элементом пользовательского интерфейса во втором окне из первого потока, вам нужно будет использовать Control.Invoke, чтобы убедиться, что фактическое манипулирование пользовательским интерфейсом происходит в правильном потоке.

Ответ 5

Я пишу приложение, которое является потоковым, и использует пользовательский интерфейс в созданном потоке для отправки функций рисования в DC.

Когда мы портировали приложение для запуска из командной строки, мы, естественно, оставались с небольшим количеством проблем, поскольку поток диспетчера не был создан или нужен, поэтому я создал другой поток из точки входа приложения, который по существу называется ShowDialog() (единственный способ вращать насос сообщений) в основной форме - с помощью скрытого OnShown для скрытия и минимизации формы при вызове. Это позволило мне по-прежнему отправлять в форму и обрабатывать весь мой чертеж из других нескольких потоков.

Это, безусловно, уродливый подход, но это был быстрый способ сделать это, и он работает так, как ожидалось.

Ответ 6

Для проекта, над которым я работаю, я создал форму, которая будет всплывать, оставаться открытой, пока задача выполняется и закрывается после.

Он содержит один ProgressBar со следующими настройками:

  • progressBar1.Style=ProgressBarStyles.Marquee
  • progressBar1.MarqueeAnimationSpeed = < - установите свою пользовательскую скорость в миллисекундах здесь

Если вы хотите, вы можете установить для свойства формы TopMost значение true.

Вот код для формы:

public partial class BusyForm : Form
{
    public BusyForm(string text = "Busy performing action ...")
    {
        InitializeComponent();
        this.Text = text;
        this.ControlBox = false;
    }

    public void Start()
    {
        System.Threading.Tasks.Task.Run(() =>
        {
            this.ShowDialog();
        });
    }

    public void Stop()
    {
        BeginInvoke((Action)delegate { this.Close(); });
    }

    public void ChangeText(string newText)
    {
        BeginInvoke((Action)delegate { this.Text = newText; });
    }
}

И вот код для использования формы в вашем коде:

        BusyForm busyForm = new BusyForm(text: "Opening database ...");

        busyForm.Start();

        //do your stuff here

        busyForm.Stop();

ОБНОВЛЕНИЕ: Я столкнулся с некоторыми основными проблемами с потоковой обработкой. Вот обновленная версия кода. Для некоторой справочной информации эта форма имеет индикатор выполнения, который отображается, когда задача занята. Я добавил команду ChangeText, чтобы показать пример того, как вы можете взаимодействовать с этой формой из другой формы. Возможно, также следует упомянуть, что ваш Main в Program.cs должен иметь атрибут [STAThread], как показано ниже.

    [STAThread]
    static void Main(string[] args)
    {
        System.Globalization.CultureInfo.DefaultThreadCurrentCulture = System.Globalization.CultureInfo.InvariantCulture;
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }