2

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

Community
  • 1
  • 1
rigamonk
  • 1,179
  • 2
  • 17
  • 45

1 Answers1

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();
Sinatr
  • 20,892
  • 15
  • 90
  • 319
  • when using Dispatcher.Invoke(() => _canvas.Children.Add(p)); i get an error:"Cannot convert lambda expression to type 'System.Delegate' because it is not a delegate type" – rigamonk May 22 '15 at 13:00
  • i converted the second suggestion to: Dispatcher.Invoke((Action)(() => _canvas.Children.Add(p))); because it wouldn't compile. But i still get an "The calling thread cannot access this object because a different thread owns it" error. Is it because I am calling LoadPicture() with a new thread? – rigamonk May 22 '15 at 13:08
  • I am not sure, which probably means my answer is wrong. Can you post complete exception message? – Sinatr May 22 '15 at 13:44