C # - how to block a GUI or event

I am trying to create a very basic version of a Simon game with WiiMote using WPF. I am stuck on how to make it rotate based where the program locks up until the GUI is displayed as a sequence.

Here is the code I have so far (mostly based on the answer here: WPF is a simple sequential animation example ):

public partial class Window1 : Window
{

    public enum SimonSquare { BLUE = 1, GREEN = 3, RED = 5, YELLOW = 7 };

    List<int> _correctSequence;
    int _currentLevel = 1;
    Random random = new Random();
    Wiimote _wiiMote;
    List<int> _squaresEntered;
    private IEnumerator<Action> _actions;
    Rectangle blueRect;
    Rectangle redRect;
    Rectangle greenRect;
    Rectangle yellowRect;

    AutoResetEvent autoEvent;

    public Window1()
    { 
        InitializeComponent(); 
        blueRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Blue, Name = "Blue"};
        redRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Red, Name = "Red" }; 
        greenRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Green, Name = "Green" };
        yellowRect = new Rectangle() { Fill = 
            System.Windows.Media.Brushes.Yellow, Name = "Yellow" };

        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(blueRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(redRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(greenRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        UniformGrid1.Children.Add(yellowRect);
        UniformGrid1.Children.Add(new Rectangle() { Fill = 
            System.Windows.Media.Brushes.LightGray });
        //connectWiiRemote();

    }

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _actions = AnimationSequence().GetEnumerator();
        autoEvent = new AutoResetEvent(false);
        Thread thread = new Thread(RunNextAction);
        thread.Start();
        autoEvent.WaitOne(); // need to block here somehow!
        int x = 5;
    }   

    IEnumerable<Action> AnimationSequence() 
    {
        getSequence();
        foreach(int square in _correctSequence)
        {
            if(square == (int) SimonSquare.BLUE)
                yield return () => animateCell(blueRect, Colors.Blue); 
            else if(square == (int) SimonSquare.RED)
                yield return () => animateCell(redRect, Colors.Red);
            else if (square == (int)SimonSquare.GREEN)
                yield return () => animateCell(greenRect, Colors.Green);
            else if (square == (int)SimonSquare.YELLOW)
                yield return () => animateCell(yellowRect, Colors.Yellow);
        }
    }

    private void animateCell(Rectangle rectangle, Color fromColor)
    {
        this.Dispatcher.BeginInvoke(new Action(delegate
        {
            Color toColor = Colors.White;
            ColorAnimation ani = new ColorAnimation(toColor, 
                new Duration(TimeSpan.FromMilliseconds(300)));
            ani.AutoReverse = true;
            SolidColorBrush newBrush = new SolidColorBrush(fromColor);
            ani.BeginTime = TimeSpan.FromSeconds(2);
            rectangle.Fill = newBrush;
            ani.Completed += (s, e) => RunNextAction();
            newBrush.BeginAnimation(SolidColorBrush.ColorProperty, ani);

        }));
    }

    private void RunNextAction()
    {
        if (_actions.MoveNext())
            _actions.Current();
        else
        {
            autoEvent.Set();
            _currentLevel++;
        }
    }

    private void getSequence()
    {
        _correctSequence = new List<int>();
        int[] values = 
            Enum.GetValues(typeof(SimonSquare)).Cast<int>().ToArray();
        for (int i = 0; i < _currentLevel + 2; i++)
        {
            _correctSequence.Add(values[random.Next(values.Length)]);
        }

    }
}

      

However autoSet waitOne / set doesn't work correctly. It currently calls RunNextAction once, but then blocks waitOne an unlimited number of times. What am I doing wrong?

EDIT: Let me try to rephrase the question. If I select Threading and AutoResetEvent, in Window_Loaded I have:

    private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        _actions = AnimationSequence().GetEnumerator();
        RunNextAction(); // shows all of the flashing squares
        // need to wait here until the flashing squares are all shown
        // process player events etc.
    }

      

When I run the above code, it will call RunNextAction once, which will keep calling itself until all the squares are shown (it looks like a native thread) BUT the WindowLoaded method keeps working. After I call RunNextAction (), I need Window_Loaded to block until RunNextAction is fully executed.

+2


source to share


4 answers


You shouldn't call WaitOne on the Dispatcher Thread!

You call WaitOne on the Dispatcher thread itself, the dispatcher thread is the main WPF APP thread, if you block it, any calls to Dispatcher.BeginInvoke or Invoke will never be called and they will wait indefinitely.

Instead, the best way to do it is to move the animation to another window called "AnimationDialog" and load it as a modal dialog.



private void window_loaded(object sender, EventArgs e){

    AnimationDialog dialog = new AnimationDialog();
    dialog.Owner = this;
    dialog.ShowDialog(); // this will wait here 

}

      

In the AnimationDialog window ...

private void Window_Loaded(object sender, EventArgs e){
    StartAnimationThread();
    // dont do any wait or block here at all...
}

// in your end of animation call "EndAnimation()"

private void EndAnimation(){
    Dispatcher.BeginInvoke()((Action)delegate(){
        this.DialogResult = true; 
        // this will stop this dialog and
        // and execute the parent window's
        // code where showdialog was called... 
    }
    )
}

      

+1


source


Maybe I don't understand your question because it seems very simple. In the Window_Loaded event, remove everything after thread.Start();

. Add a class level variable named _ImDoneSimonizing

and initialize it before false

. For any methods that accept user input, wrap this around your code:

if (_ImDoneSimonizing)
{
    // do whatever
}

      

When the animation is complete, set _ImDoneSimonizing

to true

.



Two other points:

  • It's very cool that you bring Simon back. Best Twister Game.
  • Can you create Wii games with WPF? You shook my world, sir.
0


source


AutoResetEvent does what it is designed to do, blocking thread execution until Set () is called. Perhaps it just means that you are not calling Set (). Perhaps your iterator is not working as you expect (although it looks ok without testing).

Are you calling Window_Loaded somewhere else?

0


source


You may be able to fix this problem by pumping the Windows message queue. Basically, if you send a callback to the message queue, then the calling thread will block until rendering is complete. Here is an article explaining how to do it: http://graemehill.ca/wpf-rendering-thread-synchronization

0


source







All Articles