C# - 如何阻止 GUI 或事件

发布于 2024-08-06 18:00:12 字数 4896 浏览 4 评论 0原文

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

这是我到目前为止的代码(主要基于此处的答案:WPF - 顺序动画简单示例< /a>):

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 完全完成。

I'm trying to create a very simple version of the game Simon with the WiiMote, using WPF. What I'm stuck on is how to make it turned-based, where the program blocks until the GUI is done displaying a sequence.

Here's the code I have so far (mostly based on an answer here: 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)]);
        }

    }
}

However, autoSet's waitOne/set aren't working correctly. It currently calls RunNextAction once, but then blocks on waitOne indefinitely. What am I doing wrong?

EDIT:
Let me try to rephrase the question. If I take out the 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's events etc.
    }

When I run the above code, it'll call RunNextAction once, which will keep calling itself until all of the squares are shown (it seems like on its own thread), BUT the WindowLoaded method keeps going. After I call RunNextAction(), I need Window_Loaded to block until RunNextAction is completely done.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

古镇旧梦 2024-08-13 18:00:12

您不应该在调度程序线程上调用 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... 
    }
    )
}

You should not call WaitOne on Dispatcher Thread !!

You are calling WaitOne on the Dispatcher Thread itself, the dispatcher thread is the main thread of WPF APP, if you block it, any calls to Dispatcher.BeginInvoke or Invoke will never get called and it will wait indefinately.

Instead the better way to do would be, Move your animation to another window called "AnimationDialog" and load it as Modal Dialog.

private void window_loaded(object sender, EventArgs e){

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

}

In 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... 
    }
    )
}
远昼 2024-08-13 18:00:12

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

if (_ImDoneSimonizing)
{
    // do whatever
}

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

另外两点:

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

I may be misunderstanding your question, because this seems very simple. In your Window_Loaded event, delete everything after thread.Start();. Add a class-level variable named _ImDoneSimonizing, and initialize it to false. For any methods that receive user input, wrap this around the code:

if (_ImDoneSimonizing)
{
    // do whatever
}

When the animation is done, set _ImDoneSimonizing to true.

Two other points:

  1. Very cool that you're bringing back Simon. Best game since Twister.
  2. You can create games for the Wii with WPF? You have rocked my world, sir.
梦行七里 2024-08-13 18:00:12

AutoResetEvent 正在执行其设计目的,阻止线程执行,直到调用 Set() 为止。这可能只是意味着您没有调用 Set()。也许您的迭代器没有按您期望的方式工作(尽管未经测试它看起来没问题)。

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

The AutoResetEvent is doing what it was designed to do, block thread execution until Set() is called. This probably just means you aren't calling Set(). Maybe your iterator isn't working the way you expect (although it looks ok without testing).

Are you calling Window_Loaded somewhere else?

我是有多爱你 2024-08-13 18:00:12

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

You might be able to solve this by pumping the windows message queue. Basically, if you post a callback to the message queue, then the calling thread will block until rendering is complete. Here's an article explaining how to do it: http://graemehill.ca/wpf-rendering-thread-synchronization

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文