Pergunta

Eu estou trabalhando em um jogo de cartas em C # para um projeto no meu Intro para o papel OOP e tem o jogo trabalhando agora, mas estou adicionando "toque" para o GUI.

Atualmente cartas são distribuídas e aparecem na UI instantaneamente. Quero ter a pausa programa por um momento depois de lidar um cartão antes de ser lida a seguinte.

Quando um jogo é iniciado os seguintes código é executado para preencher os PictureBoxes que os representam (será um loop eventualmente):

        cardImage1.Image = playDeck.deal().show();
        cardImage2.Image = playDeck.deal().show();
        cardImage3.Image = playDeck.deal().show();
        cardImage4.Image = playDeck.deal().show();
        cardImage5.Image = playDeck.deal().show();
        ...

Eu tenho tentativas usando System.Threading.Thread.Sleep (100); entre cada dose (). mostrar () e também dentro de cada um desses métodos, mas tudo isso alcança está travando minha GUI até que todos os sonos tenham sido processadas, então mostrar todos os cartões de uma só vez.

Eu também tentei usando uma combinação de um temporizador e while mas resultou no mesmo efeito.

Qual seria a melhor maneira de alcançar o resultado desejado?

Foi útil?

Solução

O problema é que qualquer código que você execute na UI irá bloquear a interface do usuário e congelar o programa. Quando seu código está sendo executado (mesmo se ele está correndo Thread.Sleep), mensagens (como o Paint ou clique) enviados para a interface do usuário não serão processados ??(até o controle retorna para o loop de mensagem quando sair do seu manipulador de eventos), fazendo com que ele congelar.

A melhor maneira de fazer isso é para ser executado em uma discussão de fundo, e, em seguida, Invoke para o segmento interface do usuário entre dorme, como este:

//From the UI thread,
ThreadPool.QueueUserWorkItem(delegate {
    //This code runs on a backround thread.
    //It will not block the UI.
    //However, you can't manipulate the UI from here.
    //Instead, call Invoke.
    Invoke(new Action(delegate { cardImage1.Image = playDeck.deal().show(); }));
    Thread.Sleep(100);

    Invoke(new Action(delegate { cardImage2.Image = playDeck.deal().show(); }));
    Thread.Sleep(100);

    Invoke(new Action(delegate { cardImage3.Image = playDeck.deal().show(); }));
    Thread.Sleep(100);

    //etc...
});
//The UI thread will continue while the delegate runs in the background.

Como alternativa, você poderia fazer um temporizador e mostrar cada imagem na próxima escala do temporizador. Se você usar um timer, tudo o que você deve fazer no início é iniciar o temporizador; não esperar por ele ou você vai apresentar o mesmo problema.

Outras dicas

A saída barata seria fazer um loop com chamadas para Application.DoEvents (), mas a melhor alternativa seria a criação de um System.Windows.Forms.Timer que você iria parar após o primeiro tempo que decorre. Em ambos os casos você vai precisar de algum indicador de dizer a seus manipuladores de eventos de interface do usuário para ignorar entrada. Você poderia até mesmo usar a propriedade timer.Enabled para esta finalidade, se é o suficiente simples.

Normalmente eu simplesmente recomendar uma função como esta para realizar uma pausa, permitindo a interface do usuário para ser interativo.

  private void InteractivePause(TimeSpan length)
  {
     DateTime start = DateTime.Now;
     TimeSpan restTime = new TimeSpan(200000); // 20 milliseconds
     while(true)
     {
        System.Windows.Forms.Application.DoEvents();
        TimeSpan remainingTime = start.Add(length).Subtract(DateTime.Now);
        if (remainingTime > restTime)
        {
           System.Diagnostics.Debug.WriteLine(string.Format("1: {0}", remainingTime));
           // Wait an insignificant amount of time so that the
           // CPU usage doesn't hit the roof while we wait.
           System.Threading.Thread.Sleep(restTime);
        }
        else
        {
           System.Diagnostics.Debug.WriteLine(string.Format("2: {0}", remainingTime));
           if (remainingTime.Ticks > 0)
              System.Threading.Thread.Sleep(remainingTime);
           break;
        }
     }
  }

Mas parece haver alguma complicação no uso de solução tal quando é chamado de dentro de um manipulador de eventos, tais como um clique de botão. Eu acho que o sistema quer o manipulador de clique de botão evento para voltar antes que ele irá continuar a processar outros eventos porque se eu tentar clicar novamente enquanto o manipulador de eventos ainda está em execução, o botão deprime novamente, embora eu estou tentando arrastar a forma e não clique no botão.

Então aqui está a minha alternativa. Adicionar um temporizador para o formulário e criar uma classe revendedor para lidar com punho cartões interagindo com que temporizador. Defina a propriedade Intervalo do temporizador para coincidir com o intervalo no qual você quer cartas sejam distribuídas. Aqui está o meu código de exemplo.

public partial class Form1 : Form
{

  CardDealer dealer;

  public Form1()
  {
     InitializeComponent();
     dealer = new CardDealer(timer1);
  }

  private void button1_Click(object sender, EventArgs e)
  {
     dealer.QueueCard(img1, cardImage1);
     dealer.QueueCard(img2, cardImage2);
     dealer.QueueCard(img3, cardImage1);
  }
}

class CardDealer
{
  // A queue of pairs in which the first value represents
  // the slot where the card will go, and the second is
  // a reference to the image that will appear there.
  Queue<KeyValuePair<Label, Image>> cardsToDeal;
  System.Windows.Forms.Timer dealTimer;

  public CardDealer(System.Windows.Forms.Timer dealTimer)
  {
     cardsToDeal = new Queue<KeyValuePair<Label, Image>>();
     dealTimer.Tick += new EventHandler(dealTimer_Tick);
     this.dealTimer = dealTimer;
  }

  void dealTimer_Tick(object sender, EventArgs e)
  {
     KeyValuePair<Label, Image> cardInfo = cardInfo = cardsToDeal.Dequeue();
     cardInfo.Key.Image = cardInfo.Value;
     if (cardsToDeal.Count <= 0)
        dealTimer.Enabled = false;
  }

  public void QueueCard(Label slot, Image card)
  {
     cardsToDeal.Enqueue(new KeyValuePair<Label, Image>(slot, card));
     dealTimer.Enabled = true;
  }
}

Gostaria de tentar põr o código que trata o deck (e chama Thread.Sleep) em outro segmento.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top