C # - как заблокировать для GUI или события - PullRequest
2 голосов
/ 03 октября 2009

Я пытаюсь создать очень простую версию игры Simon с WiiMote, используя WPF. Я застрял на том, как сделать его пошаговым, когда программа блокируется до тех пор, пока графический интерфейс не будет отображен.

Вот код, который у меня есть (в основном на основе ответа здесь: WPF - простой пример последовательной анимации ):

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)]);
        }

    }
}

Тем не менее, waitOne / set autoSet работают неправильно. В настоящее время он вызывает RunNextAction один раз, но затем блокирует waitOne на неопределенный срок. Что я делаю не так?

EDIT: Позвольте мне попытаться перефразировать вопрос. Если я возьму Threading и AutoResetEvent, в Window_Loaded у меня есть:

    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's events etc.
    }

Когда я запускаю приведенный выше код, он будет вызывать RunNextAction один раз, который будет вызывать сам себя до тех пор, пока не будут показаны все квадраты (кажется, как в собственном потоке), НО метод WindowLoaded продолжает работать. После того, как я вызываю RunNextAction (), мне нужно Window_Loaded для блокировки, пока RunNextAction не будет полностью завершен.

Ответы [ 4 ]

1 голос
/ 03 октября 2009

Вы не должны вызывать WaitOne в ветке Dispatcher !!

Вы вызываете WaitOne для самого потока диспетчера, поток диспетчера является основным потоком WPF APP, если вы его заблокируете, любые вызовы Dispatcher.BeginInvoke или Invoke никогда не будут вызываться и будут ждать неопределенно долго.

Вместо этого лучший способ сделать это - переместить анимацию в другое окно с именем «AnimationDialog» и загрузить его как модальное диалоговое окно.

private void window_loaded(object sender, EventArgs e){

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

}

В окне диалога анимации ...

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... 
    }
    )
}
0 голосов
/ 04 октября 2009

Возможно, вам удастся решить эту проблему путем прокачки очереди сообщений Windows. По сути, если вы отправляете обратный вызов в очередь сообщений, то вызывающий поток будет блокироваться до завершения рендеринга. Вот статья, объясняющая, как это сделать: http://graemehill.ca/wpf-rendering-thread-synchronization

0 голосов
/ 03 октября 2009

AutoResetEvent делает то, для чего был предназначен, блокирует выполнение потока до вызова Set (). Это, вероятно, просто означает, что вы не вызываете Set (). Возможно, ваш итератор работает не так, как вы ожидаете (хотя без тестирования он выглядит нормально).

Вы вызываете Window_Loaded где-то еще?

0 голосов
/ 03 октября 2009

Возможно, я неправильно понимаю ваш вопрос, потому что это кажется очень простым. В вашем событии Window_Loaded удалите все после thread.Start();. Добавьте переменную уровня класса с именем _ImDoneSimonizing и инициализируйте ее как false. Для любых методов, которые получают пользовательский ввод, оберните это вокруг кода:

if (_ImDoneSimonizing)
{
    // do whatever
}

Когда анимация завершится, установите _ImDoneSimonizing на true.

Два других пункта:

  1. Очень круто, что ты возвращаешь Саймона. Лучшая игра со времен Twister.
  2. Вы можете создавать игры для Wii с WPF? Вы потрясли мой мир, сэр.
...