【问题标题】:C# - how to block for GUI or an eventC# - 如何阻止 GUI 或事件
【发布时间】:2009-10-04 06:18:47
【问题描述】:

我正在尝试使用 WPF 使用 WiiMote 创建一个非常简单的 Simon 游戏版本。我一直坚持的是如何使它基于回合制,程序会阻塞,直到 GUI 完成显示序列。

这是我到目前为止的代码(主要基于此处的答案:WPF - sequential animation simple 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)]);
        }

    }
}

但是,autoSet 的 waitOne/set 不能正常工作。它当前调用 RunNextAction 一次,但随后无限期地阻塞在 waitOne 上。我做错了什么?

编辑: 让我试着重新表述这个问题。如果我取出 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 完全完成。

【问题讨论】:

  • 您的意思是在播放动画时不接受输入?
  • @RCIX 是的,有点。由于 RunActionOnce 有点递归,我需要最后一次调用它(即显示最后一个方块)来通知调用者(Window_Loaded),以便它可以处理玩家的动作。

标签: c# wpf user-interface synchronization blocking


【解决方案1】:

你不应该在 Dispatcher Thread 上调用 WaitOne !!

你在 Dispatcher Thread 本身上调用 WaitOne,dispatcher 线程是 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... 
    }
    )
}

【讨论】:

    【解决方案2】:

    我可能误解了你的问题,因为这看起来很简单。在您的 Window_Loaded 事件中,删除 thread.Start(); 之后的所有内容。添加一个名为_ImDoneSimonizing 的类级变量,并将其初始化为false。对于任何接收用户输入的方法,将其包裹在代码中:

    if (_ImDoneSimonizing)
    {
        // do whatever
    }
    

    动画完成后,将_ImDoneSimonizing 设置为true

    另外两点:

    1. 很酷,你把西蒙带回来了。自 Twister 以来的最佳游戏。
    2. 您可以使用 WPF 为 Wii 创建游戏吗?先生,你震撼了我的世界。

    【讨论】:

    • 这不是 Wii 本身,它实际上是一个学生项目。 WiiMote 可以与蓝牙加密狗交谈,这里有一个非常好的 C# 库用于处理 WiiMote 事件:blogs.msdn.com/coding4fun/archive/2007/03/14/1879033.aspx。卡内基梅隆大学的研究员 Johnny Lee 提出了一些非常巧妙的方法来扩展 WiiMote 的用途,并在 johnnylee.net/projects/wii 上提供了一些非常简洁的视频。
    • @David:我还是撒了谎——西蒙糟透了。 :)
    • @David:我在 youtube 上看到了 Johnny Lee 的一些 WiiMote 项目,它们看起来非常酷。祝你好运!
    【解决方案3】:

    AutoResetEvent 正在执行其设计的任务,在调用 Set() 之前阻止线程执行。这可能只是意味着您没有调用 Set()。也许您的迭代器没有按照您期望的方式工作(尽管没有测试它看起来还可以)。

    您是在其他地方调用 Window_Loaded 吗?

    【讨论】:

    • 不,Window_Loaded 应该只被调用一次。也许问题在于使用 Dispatcher 调用 RunNextAction?
    【解决方案4】:

    您也许可以通过抽取 Windows 消息队列来解决此问题。基本上,如果您向消息队列发布回调,则调用线程将阻塞,直到渲染完成。这是一篇解释如何做到这一点的文章:http://graemehill.ca/wpf-rendering-thread-synchronization

    【讨论】:

      猜你喜欢
      • 2021-12-11
      • 1970-01-01
      • 2013-03-08
      • 2017-08-07
      • 1970-01-01
      • 2014-09-23
      • 2010-11-25
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多