3

In my c# program I create a bunch of buttons in the main (UI) thread. Then I start a timer that runs a method Timeline(). Now I want the appearance of the buttons to change when the Timeline() method runs. But I'm not allowed to access the objects because they are owned by the main thread. How do I solve this?

Error: The calling thread cannot access this object because a different thread owns it.

I have found Dispatcher disp = Dispatcher.CurrentDispatcher; which has an Invoke() method but I'm not sure if that is the right way, and I couldn't get it to work.

Here is part of my code:

    private static SurfaceButton[,] buttons = new SurfaceButton[dimensionBeats, dimensionsNotes];

    public SurfaceWindow1()
    {
        try
        {
            InitializeComponent();
        }
        catch (Exception ex)
        {
        }


        LoadAudioContent();
        // Add handlers for Application activation events
        AddActivationHandlers();


        InitializeButtons();

        try
        {
            t = new Timer(Timeline, null, 1, dimensionBeats*500);
        }
        catch (Exception e)
        {

        } 
    }

    private void InitializeButtons()
    {
        for (int i = 0; i < 8; i++)
        {
            for (int j = 0; j < 6; j++)
            {
                SurfaceButton btn = new SurfaceButton();
                //btn.Content = files[j].Substring(0,1);
                btn.Name = "a" +i + j;
                btn.Width = 120;
                btn.Height = 120;
                btn.ContactEnter+=StartSound;
                btn.ContactLeave+=StopSound;
                btn.Opacity = 0.01;
                btn.ClickMode = ClickMode.Release;

                buttons[i,j] = btn;

                // add the newly generated button to the correct column (left to right one panel per column)
                switch (i)
                {
                    case 0:
                        rowPanel0.Children.Add(btn);
                        break;
                    case 1:
                        rowPanel1.Children.Add(btn);
                        break;
                    case 2:
                        rowPanel2.Children.Add(btn);
                        break;
                    case 3:
                        rowPanel3.Children.Add(btn);
                        break;
                    case 4:
                        rowPanel4.Children.Add(btn);
                        break;
                    case 5:
                        rowPanel5.Children.Add(btn);
                        break;
                    case 6:
                        rowPanel6.Children.Add(btn);
                        break;
                    case 7:
                        rowPanel7.Children.Add(btn);
                        break;
                }
            }
        }
    }


    private void Timeline(Object state)
    {     

        for (int i = 0; i < pressed.GetLength(0); i++)
        {
            sendRequest(url, postMethod, tabelId, buttsToString(butts));

            for (int j = 0; j < pressed.GetLength(1); j++)
            {
                if (pressed[i,j])
                {
                    buttons[i, j].Background = Brushes.Red;
                }
            }
            Thread.Sleep(500);
            for (int j = 0; j < pressed.GetLength(1); j++)
            {
                buttons[i, j].Background = Brushes.White;
            }
        }
Niels
  • 1,340
  • 2
  • 15
  • 32

4 Answers4

3

You need to use Invoke/BeginInvoke to forward updates to the GUI thread:

int i1 = i; // local copies to avoid closure bugs
int j1 = j;
buttons[i1, j1].Dispatcher.BeginInvoke(
    ((Action)(() => buttons[i1, j1].Background = Brushes.Red)));
Tudor
  • 61,523
  • 12
  • 102
  • 142
  • 1
    Then I get the error: A first chance exception of type 'System.IndexOutOfRangeException' occurred in CoffeeTable1.exe. – Niels Oct 09 '12 at 07:33
  • @Niels: I suspect the problem is variable capture. Check my edited code. – Tudor Oct 09 '12 at 07:38
  • You were right. Thanks! I never heard of variable capture before. Any suggestion where to learn how and why it works? – Niels Oct 09 '12 at 07:42
  • 1
    @Niels: Here's the definitive blog post about this common issue: http://blogs.msdn.com/b/ericlippert/archive/2009/11/12/closing-over-the-loop-variable-considered-harmful.aspx And sorry for the terminology, it's actually closure, not capture. :) – Tudor Oct 09 '12 at 07:44
1

This is how you do it-

Button1.Invoke(new MethodInvoker(delegate {
// Executes the following code on the GUI thread.
Button1.BackColor="Red";}));
Shiridish
  • 4,942
  • 5
  • 33
  • 64
  • If I try this one I get: Error 3 'Microsoft.Surface.Presentation.Controls.SurfaceButton' does not contain a definition for 'Invoke' and no extension method 'Invoke' accepting a first argument of type 'Microsoft.Surface.Presentation.Controls.SurfaceButton' could be found (are you missing a using directive or an assembly reference?) – Niels Oct 09 '12 at 07:19
  • This is how you usually do it with the controls in .NET. Sorry but no idea of Surface Buttons and Microsoft surface. – Shiridish Oct 09 '12 at 07:35
  • This is the WinForms version. He's using WPF. – Tudor Oct 09 '12 at 07:37
  • I think it should have been `this.Dispatcher.Invoke(` – Niels Oct 09 '12 at 07:43
0

Try wrapping the access to the button or the whole (loop around it) with

Application.Current.Dispatcher.BeginInvoke((ThreadStart)delegate
{
    // TODO: Implement task to do on main thread
});

Controls must always be accessed on the main thread.

Akku
  • 4,373
  • 4
  • 48
  • 67
0

Yes will need to make use of Invoke method. It executes the specified delegate synchronously on the thread the Dispatcher is associated with.

Saurabh R S
  • 3,037
  • 1
  • 34
  • 44