Изменить тип потока на STA

189
10 февраля 2019, 22:50

Есть задача выполнить длительную по времени операция с контролом (WPF), дабы она не блокировала UI решил подсмотреть вариант здесь. По итогу вышел вот такой метод

async void PrintMethod()
        {
            try
            {
                await Task.Factory.StartNew(() => messageView.PrintDirect(), TaskCreationOptions.LongRunning);
            }
            catch(Exception ex)
            {
                logger.Error("Ошибка печати: {0}", ex.Message);
            }
        }

Однако, получаю ошибку следующего содержания: "Вызывающим потоком должен быть STA, поскольку этого требуют большинство компонентов UI". Если бы я создавал поток с использованием Thread, я бы использовал метод SetApartmentState(ApartmentState.STA), а вот что делать в таком случае я не знаю. Есть какие-нибудь вариаты?

Answer 1

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

BusyWindow _busyWindow = null;
object _busyWindowSync = new object();
private void ShowBusy()
{
    lock (_busyWindowSync)
    {
        if (_busyWindow == null)
        {
            double left = Dispatcher.Invoke((Func<double>)(() => this.Left + this.Width / 2));
            double top = Dispatcher.Invoke((Func<double>)(() => this.Top + this.Height / 2));
            Thread newWindowThread = new Thread(new ParameterizedThreadStart(AnimationThreadStartingPoint));
            newWindowThread.SetApartmentState(ApartmentState.STA);
            newWindowThread.IsBackground = true;
            newWindowThread.Start(new Point() { X = left, Y = top });
        }
    }
}
private void AnimationThreadStartingPoint(object position)
{
    lock (_busyWindowSync)
    {
        if (_busyWindow == null)
        {
            _busyWindow = new BusyWindow();
            _busyWindow.Left = ((Point)position).X;
            _busyWindow.Top = ((Point)position).Y;
            _busyWindow.Show();
        }
    }
    System.Windows.Threading.Dispatcher.Run();
}
private void HideBusy()
{
    lock (_busyWindowSync)
    {
        if (_busyWindow != null)
        {
            _busyWindow.Dispatcher.BeginInvoke((Action)_busyWindow.Close);
        }
    }
}

BusyWindow - это класс окна показываемого в отдельном потоке. Пример вот от сюда, и там это окно использовалось для анимации, которую нельзя отобразить в основном окне. Вы, наоборот, можете в этом окне выполнять длительную операцию, чтобы не подвисало основное окно. Ну или прямо взять этот пример и пока у вас "висит" главное окно, пользователь будет видеть спиннер.

Answer 2

По итогу я решил свою проблему следующим способом:

  1. Создал окно WPF с разметкой из одного требуемого мне контрола - полной копией того, что в основном окне.
  2. Создание этого окна сделал в отдельном потоке, отображал окно с помощью ShowDialog() так как он не дает потоку завершиться раньше времени.

    Thread hidePrintThread = new Thread(PrintMethod); hidePrintThread.SetApartmentState(ApartmentState.STA); hidePrintThread.IsBackground = true; hidePrintThread.Start();

    void PrintMethod()
        {
            try
            {
                CriteriaOperator co=null;
                List<MessageGridContent> mess = Service.Messages.ToList();
                Dispatcher.Invoke(() => co = messageGrid.FilterCriteria);
                HiddenPrintWindow window = new HiddenPrintWindow();
                window.MessagesToPrint = mess;
                window.CriteriaOperator = co;
                Service.Printing = true;
                window.ShowDialog();                
            }
            catch(Exception ex)
            {
                logger.Error("Ошибка печати: {0}", ex.Message);
            }
        }   
  3. Так как мне необходимо было, чтобы данная длительная операция проходила незаметно для пользователя, я решил скрыть окно. Стандартные методы работать отказывались. Я пробовал прописывать Visibyliti в разметке, пробовал в обработчике события загрузки окна, пробовал в методе, который запускается из обработчика загрузки, но окно упрямо висело видимым, тогда я убрал его на задний план (тоже подходило по ТЗ) с помощью WinAPI:

[DllImport("user32.dll")] static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

Код инициализации и загрузки самого окна:

public HiddenPrintWindow()
        {
            InitializeComponent();    
            Loaded += HiddenPrintWindow_Loaded;
        }
private void HiddenPrintWindow_Loaded(object sender, RoutedEventArgs e)
            {
                SetWindowPos(new System.Windows.Interop.WindowInteropHelper(this).Handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
                Work();
            }

И спасибо всем помогавшим!!!

READ ALSO
не работает плавный прыжок в Unity2D

не работает плавный прыжок в Unity2D

Нашел в интернете 3 варианта, попробовал все, но прыжок все равно резкий (будто телепорт)Видел совет умножать на Time

191
Посчитать количество писем в Outlook используя C#

Посчитать количество писем в Outlook используя C#

Всем привет! Задача такая - необходимо получить количество писем за определенный промежуток времениК примеру, за прошедший месяц

240
c# как сделать NTLM сквозную авторизацию на сервере

c# как сделать NTLM сквозную авторизацию на сервере

Как авторизироваться через LDAP, или через какую-то библиотеку

221
Авто генерация итемов кроме ListView

Авто генерация итемов кроме ListView

Товарищи, доброго времени суток! Чисто теоретический вопрос, есть понимание, как настроить автогенерацию итемов в ListView через ViewModel, путем...

184