Showing busy indicator on a STA thread

2019-03-01 14:52发布

问题:

I have a long operation wehre I'd like to show the Extended Toolkits busy indicator. I made a previous post about this and it was fixed Wpf Extended toolkit BusyIndicator not showing during operation. However, during that call I have to interact with a UI element (canvas) and I get a "The calling thread must be STA, because many UI components require this". I understand (now) that a background worker(see code):

     private void CboItemId_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {

        BackgroundWorker _backgroundWorker = new BackgroundWorker();
        _backgroundWorker.DoWork += new DoWorkEventHandler(backgroundWorker_DoWork);
        _backgroundWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_backgroundWorker_RunWorkerCompleted);
        ItemSearchBusyIndicator.IsBusy = true;

       // Mouse.OverrideCursor = System.Windows.Input.Cursors.Wait;
        if (RdoItemSearch.IsChecked == false) return;
        ///backgroundWorker_DoWork(null, null);

        if (CboItemId.SelectedValue == null) return;
        if (CboItemId.SelectedValue.ToString() != string.Empty)
        {
            selectedItem = CboItemId.SelectedValue.ToString();
            _backgroundWorker.RunWorkerAsync();
        }
       // Mouse.OverrideCursor = System.Windows.Input.Cursors.Arrow;
    }

    public void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
    {

            LoadItemData(selectedItem);
    }

uses MTA and cannot be set to STA. So i tried calling the internal function that uses the UI elelment in its own thread:

       public void LoadItemData(string itemId)
    {

        Axapta ax = new Axapta();
        files.Clear();
        try
        {
            ax.Logon(Settings.Default.Server, null, Settings.Default.Test, null);
            AxaptaContainer path = (AxaptaContainer)ax.CallStaticClassMethod(Settings.Default.ClassName, Settings.Default.ItemData, itemId);
            for (int i = 1; i <= path.Count; i++)
            {
                AxaptaContainer somestring = (AxaptaContainer)path.get_Item(i);
                for (int j = 1; j <= somestring.Count; j += 2)
                {
                    string extension = Path.GetExtension(somestring.get_Item(j + 1).ToString().ToLower());
                    if (extension == ".jpg"
                        || extension == ".jpeg"
                        || extension == ".gif"
                        || extension == ".png"
                        || extension == ".bmp"
                        || extension == ".pdf")
                        /* key=path - value=description */
                        files.Add(somestring.get_Item(j + 1).ToString(), somestring.get_Item(j).ToString());
                }
            }

           // _canvas.Children.Clear();
            Thread t = new Thread(new ThreadStart(LoadPictures));
            t.SetApartmentState(ApartmentState.STA);
            t.Start();

        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
        finally
        {
            ax.Logoff();
        }
    }

Heres where I interact with the canvas element:

      private void LoadPictures()
    {

        foreach (DictionaryEntry filePath in files)
        {

            try
            {

                Picture p = new Picture();
                ToolTip t = new ToolTip();
                t.Content = filePath.Value;
                p.ToolTip = t;
                TextBlock tb = new TextBlock();
                tb.Text = filePath.Value.ToString();
                Canvas.SetTop(tb, y);
                Canvas.SetLeft(tb, x);

                    p.ImagePath = filePath.Key.ToString();
                    p.OriginalImagePath = filePath.Key.ToString();
                    p.ImageName = filePath.Value.ToString();
                    _canvas.Children.Add(p); //<-------This is where i seem to error
            }
            catch (Exception ex)
            {
                MessageBox.Show("Error:" + ex.Message,"File Load Error",MessageBoxButton.OK,MessageBoxImage.Error);
            }
        }
    }

but I get a "The calling thread cannot access this object because a different thread owns it" I don't know how to call the long running (LoadItemData()) function while showing the BusyIndicator without a backgroundworker. Any help appreciated

回答1:

There are multiple approaches:

1) Async binding, it's not recommended, but it is there. You can run long running task in property getter, framework will prevent UI from blocking, when it is finished - UI will get updated.

2) Use BackgroundWorker or Task/Thread to run code, but invoke it into UI thread. In your example:

Dispatcher.InvokeAsync(() => _canvas.Children.Add(p));

3) You can block UI thread of main window completely, no problems. But to indicate about its being busy you can create window in another thread and show there busy status (run animations, etc):

        var thread = new Thread(() =>
        {
            var window = new SomeWindow();
            window.ShowDialog();
        });
        thread.SetApartmentState(ApartmentState.STA);
        thread.IsBackground = true;
        thread.Start();