Как я могу создать элементы управления WPF в фоновом потоке? - PullRequest
10 голосов
/ 04 мая 2010

У меня есть метод, который создает фоновый поток, чтобы сделать некоторые действия. В этой фоновой теме я создаю объект. Но этот объект при создании во время выполнения дает мне исключение:

Вызывающим потоком должен быть STA, поскольку это требуется для многих компонентов пользовательского интерфейса.

Я знаю, что должен использовать Dispatcher, чтобы отражать что-то в пользовательском интерфейсе. Но в этом случае я просто создаю объект и не использую интерфейс. Это мой код:

    public void SomeMethod()
      {
         BackgroundWorker worker = new BackgroundWorker();
         worker.DoWork += new DoWorkEventHandler(Background_Method);
         worker.RunWorkerAsync();
      }

   void Background_Method(object sender, DoWorkEventArgs e)
      {
         TreeView tv = new TreeView();
      }

Как я могу создавать объекты в фоновом потоке?

Я использую приложение WPF

Ответы [ 8 ]

6 голосов
/ 04 мая 2010

TreeView - это элемент управления пользовательского интерфейса. Вы можете создавать элементы управления пользовательским интерфейсом и управлять ими только в потоке пользовательского интерфейса, поэтому то, что вы пытаетесь сделать, невозможно.

То, что вы хотите сделать, это выполнить всю трудоемкую работу в фоновом потоке, а затем «перезвонить» потоку пользовательского интерфейса для управления пользовательским интерфейсом. На самом деле это довольно просто:

void Background_Method(object sender, DoWorkEventArgs e)
{
    // ... time consuming stuff...

    // call back to the window to do the UI-manipulation
    this.BeginInvoke(new MethodInvoker(delegate {
        TreeView tv = new TreeView();
        // etc, manipulate
    }));
}

Возможно, у меня неправильный синтаксис для BeginInvoke (это не в моей голове), но все равно вы идете ...

3 голосов
/ 04 мая 2010

НТН:

    void Background_Method(object sender, DoWorkEventArgs e)
    {
        // Time Consuming operations without using UI elements
        // Result of timeconsuming operations
        var result = new object();
        App.Current.Dispatcher.Invoke(new Action<object>((res) =>
            {
                // Working with UI
                TreeView tv = new TreeView();
            }), result);
    }
2 голосов
/ 26 февраля 2017

Никто не обсуждает случай отдельного потока STA в деталях (даже если концепция точно такая же).

Итак, давайте представим простой элемент управления вкладкой, добавляемый при нажатии кнопки

    private void button_Click(object sender, RoutedEventArgs e)
    {
        TabItem newTab = new TabItem() { Header = "New Tab" };
        tabMain.Items.Add(newTab);
    }

Если мы переместим его в другой поток STA

    private void button_Click(object sender, RoutedEventArgs e)
    {
        Thread newThread = new Thread(new ThreadStart(ThreadStartingPoint));
        newThread.SetApartmentState(ApartmentState.STA);
        newThread.IsBackground = true;
        newThread.Start();
    }
    private void ThreadStartingPoint()
    {
        TabItem newTab = new TabItem() { Header = "New Tab" };
        tabMain.Items.Add(newTab);
    }

конечно мы получаем System.InvalidOperationException

Теперь, что произойдет, если мы добавим элемент управления

    private void AddToParent(string header)
    {
        TabItem newTab = new TabItem() { Header = header };
        tabMain.Items.Add(newTab);
    }

с использованием метода делегата?

    public void DelegateMethod(string header)
    {
        tabMain.Dispatcher.BeginInvoke(
                new Action(() => {
                    this.AddToParent(header);
                }), null);
    }

это работает, если вы называете это

    private void button_Click(object sender, RoutedEventArgs e)
    {
        Thread newThread = new Thread(new ThreadStart(ThreadStartingPoint));
        newThread.SetApartmentState(ApartmentState.STA);
        newThread.IsBackground = true;
        newThread.Start();
    }
    private void ThreadStartingPoint()
    {
        DelegateMethod("new tab");
    }

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

0 голосов
/ 04 мая 2010

Смотрите ответ на этот вопрос: Как запустить что-то в потоке STA?

Когда вы определяете свою нить, установите для ApartmentState значение STA:

thread.SetApartmentState(ApartmentState.STA);

Это должно сработать!

0 голосов
/ 04 мая 2010

Я решил свою проблему. Я просто использовал свойство e.Result метода RunWorkerCompleted. Я получаю данные в фоновом потоке, а затем использую эти данные после завершения потока. Спасибо всем за полезные методы. Отдельное спасибо Veer за рекомендацию о e.Result собственности.

0 голосов
/ 04 мая 2010
void Background_Method(object sender, DoWorkEventArgs e) 
{ 
    TreeView tv = new TreeView(); 
    // Generate your TreeView here
    UIDispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(() => 
    { 
        someContainer.Children.Add(tv);
    }; 
}
0 голосов
/ 04 мая 2010

Попробуйте следующий код:

public void SomeMethod() 
{ 

System.ComponentModel.BackgroundWorker myWorker = new  System.ComponentModel.BackgroundWorker();

myWorker.DoWork += myWorker_DoWork;

myWorker.RunWorkerAsync();

}

private void myWorker_DoWork(object sender,
   System.ComponentModel.DoWorkEventArgs e)
{
   // Do time-consuming work here
}
0 голосов
/ 04 мая 2010

Чтобы ваш код просто работал, вы должны присоединиться к квартире STA COM, позвонив по номеру Thread.SetApartmentState(ApartmentState.STA). Поскольку BackgroundWorker, вероятно, использует некоторый общий пул потоков, присоединение к определенной квартире может повлиять на других пользователей этого пула потоков или даже может завершиться ошибкой, если для него уже установлено, например MTA раньше. Даже если все получится, ваш вновь созданный TreeView будет заблокирован для этого рабочего потока. Вы не сможете использовать его в своем основном потоке пользовательского интерфейса.

Если бы вы немного подробнее объяснили свои истинные намерения, вам наверняка помогут лучше.

...