Onde está o Application.DoEvents () no WPF?


89

Tenho o seguinte código de amostra que aumenta o zoom sempre que um botão é pressionado:

XAML:

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Canvas x:Name="myCanvas">

        <Canvas.LayoutTransform>
            <ScaleTransform x:Name="myScaleTransform" />
        </Canvas.LayoutTransform> 

        <Button Content="Button" 
                Name="myButton" 
                Canvas.Left="50" 
                Canvas.Top="50" 
                Click="myButton_Click" />
    </Canvas>
</Window>

* .cs

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    private void myButton_Click(object sender, RoutedEventArgs e)
    {
        Console.WriteLine("scale {0}, location: {1}", 
            myScaleTransform.ScaleX,
            myCanvas.PointToScreen(GetMyByttonLocation()));

        myScaleTransform.ScaleX =
            myScaleTransform.ScaleY =
            myScaleTransform.ScaleX + 1;

        Console.WriteLine("scale {0}, location: {1}",
            myScaleTransform.ScaleX,
            myCanvas.PointToScreen(GetMyByttonLocation()));
    }

    private Point GetMyByttonLocation()
    {
        return new Point(
            Canvas.GetLeft(myButton),
            Canvas.GetTop(myButton));
    }
}

a saída é:

scale 1, location: 296;315
scale 2, location: 296;315

scale 2, location: 346;365
scale 3, location: 346;365

scale 3, location: 396;415
scale 4, location: 396;415

como podem ver, tem um problema, que pensei resolver usando Application.DoEvents();mas ... não existe a priori no .NET 4.

O que fazer?


8
Threading? Application.DoEvents () foi o substituto do pobre homem para escrever aplicativos multithread corretamente e prática extremamente pobre em qualquer caso.
— Colin Mackay

2
Eu sei que isso é ruim e ruim, mas prefiro algo que nada.
— serhio

Respostas:


25

O antigo método Application.DoEvents () foi descontinuado no WPF em favor do uso de um Dispatcher ou um Background Worker Thread para fazer o processamento conforme você descreveu. Veja os links para alguns artigos sobre como usar os dois objetos.

Se você realmente precisar usar Application.DoEvents (), poderá simplesmente importar o system.windows.forms.dll para o seu aplicativo e chamar o método. No entanto, isso realmente não é recomendado, pois você está perdendo todas as vantagens que o WPF oferece.


1
Eu sei que é ruim e ruim, mas prefiro algo que nada ... Como posso usar o Dispatcher na minha situação?
— serhio

3
Eu entendo sua situação. Eu estava lá quando escrevi meu primeiro aplicativo WPF, mas fui em frente e dediquei um tempo para aprender a nova biblioteca e me tornei muito melhor no longo prazo. Eu recomendo fortemente que você reserve um tempo. Quanto ao seu caso específico, parece-me que você deseja que o despachante controle a exibição das coordenadas sempre que o evento de clique for disparado. Você precisa ler mais no Dispatcher para a implementação exata.
— Dillie-O

13
Remover Application.DoEvents () é quase tão irritante quanto MS remover o botão "Iniciar" no Windows 8.
— JeffHeaton

2
Não, foi uma coisa boa, esse método causou mais mal do que bem.
— Jesse de

1
Não posso acreditar que isso ainda seja um problema nos dias de hoje. Lembro-me de ter que chamar DoEvents em VB6 para simular o comportamento assíncrono. O problema é que um trabalhador em segundo plano só funciona quando você não está fazendo o processamento da IU, mas o processamento da IU - como, por exemplo, a criação de milhares de ListBoxItems pode bloquear o thread da IU. O que devemos fazer quando há um trabalho realmente pesado de IU a ser feito?
— Christian Findlay

136

Tente algo assim

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}

1
Até escrevi um método de extensão para aplicação :)public static void DoEvents(this Application a)
— serhio

@serhio: Método de extensão bacana :)
— Fredrik Hedblad

3
Devo observar, entretanto, que na aplicação real Application.Currentàs vezes é nulo ... então talvez não seja totalmente equivalente.
— serhio

Perfeito. Esta deve ser a resposta. Os opositores não deveriam receber o crédito.
— Jeson Martajaya

7
Isso nem sempre funcionará, pois não empurra o quadro, se uma instrução de interrupção for feita (ou seja, uma chamada para um método WCF que em sincronia continue com este comando), é provável que você não veja 'atualizar', pois será bloqueado .. É por isso que a resposta flq fornecida a partir do recurso MSDN é mais correta do que esta.
— GY

58

Bem, acabei de chegar a um caso em que comecei a trabalhar em um método que é executado no thread do Dispatcher e ele precisa ser bloqueado sem bloquear o UI Thread. Acontece que o msdn explica como implementar um DoEvents () com base no próprio Dispatcher:

public void DoEvents()
{
    DispatcherFrame frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(ExitFrame), frame);
    Dispatcher.PushFrame(frame);
}

public object ExitFrame(object f)
{
    ((DispatcherFrame)f).Continue = false;

    return null;
}

(retirado do método Dispatcher.PushFrame )

Alguns podem preferir em um único método que aplicará a mesma lógica:

public static void DoEvents()
{
    var frame = new DispatcherFrame();
    Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
        new DispatcherOperationCallback(
            delegate (object f)
            {
                ((DispatcherFrame)f).Continue = false;
                return null;
            }),frame);
    Dispatcher.PushFrame(frame);
}

Belo achado! Isso parece mais seguro do que a implementação sugerida por Meleak. Encontrei uma postagem no blog sobre isso
— HugoRune

2
@HugoRune Essa postagem do blog afirma que essa abordagem é desnecessária e que deve ser usada a mesma implementação do Meleak.
— Lukazoid

1
@Lukazoid Pelo que eu posso dizer, a implementação simples pode causar travamentos difíceis de rastrear. (Não tenho certeza sobre a causa, possivelmente o problema é o código na fila do despachante chamando DoEvents novamente, ou o código na fila do despachante gerando mais quadros do despachante.) Em qualquer caso, a solução com exitFrame não apresentou tais problemas, então eu recomendo aquele. (Ou, claro, não usar doEvents)
— HugoRune

1
Mostrar uma sobreposição em sua janela em vez de uma caixa de diálogo em combinação com a maneira do caliburn de envolver VMs quando o aplicativo está fechando descartou callbacks e nos obrigou a bloquear sem bloquear. Eu ficaria muito feliz se você me apresentasse uma solução sem o hack DoEvents.
— flq

1
novo link para a postagem antiga do blog: kent-boogaart.com/blog/dispatcher-frames
— CAD bloke

13

Se você precisa apenas atualizar o gráfico da janela, é melhor usar assim

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}

1
Usando DispatcherPriority.Render trabalhe mais rápido que DispatcherPriority.Background. Testado hoje com StopWatcher
— Александр Пекшев

Acabei de usar esse truque, mas use DispatcherPriority.Send (prioridade mais alta) para obter melhores resultados; UI mais responsiva.
— Bent Rasmussen

6
myCanvas.UpdateLayout();

parece funcionar bem.


Vou usar isso porque parece muito mais seguro para mim, mas vou manter os DoEvents para outros casos.
— Carter Medlin

Não sei por que, mas isso não funciona para mim. DoEvents () funciona bem.
— newman

No meu caso, tive que fazer isso tão bem quanto DoEvents ()
— Jeff

3

Um problema com as duas abordagens propostas é que elas implicam no uso ocioso da CPU (até 12% na minha experiência). Isso não é ideal em alguns casos, por exemplo, quando o comportamento modal da interface do usuário é implementado usando esta técnica.

A variação a seguir introduz um atraso mínimo entre os quadros usando um temporizador (observe que está escrito aqui com Rx, mas pode ser obtido com qualquer temporizador regular):

 var minFrameDelay = Observable.Interval(TimeSpan.FromMilliseconds(50)).Take(1).Replay();
 minFrameDelay.Connect();
 // synchronously add a low-priority no-op to the Dispatcher's queue
 Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => minFrameDelay.Wait()));

1

Desde a introdução de asynce awaitagora é possível abandonar o thread da IU parcialmente por meio de um (anteriormente) * bloco síncrono de código usando Task.Delay, por exemplo,

private async void myButton_Click(object sender, RoutedEventArgs e)
{
    Console.WriteLine("scale {0}, location: {1}", 
        myScaleTransform.ScaleX,
        myCanvas.PointToScreen(GetMyByttonLocation()));

    myScaleTransform.ScaleX =
        myScaleTransform.ScaleY =
        myScaleTransform.ScaleX + 1;

    await Task.Delay(1); // In my experiments, 0 doesn't work. Also, I have noticed
                         // that I need to add as much as 100ms to allow the visual tree
                         // to complete its arrange cycle and for properties to get their
                         // final values (as opposed to NaN for widths etc.)

    Console.WriteLine("scale {0}, location: {1}",
        myScaleTransform.ScaleX,
        myCanvas.PointToScreen(GetMyByttonLocation()));
}

Vou ser honesto, não tentei com o código exato acima, mas eu o uso em loops apertados quando estou colocando muitos itens em um ItemsControlque tem um modelo de item caro, às vezes adicionando um pequeno atraso para dar o outro coisas na IU mais tempo.

Por exemplo:

        var levelOptions = new ObservableCollection<GameLevelChoiceItem>();

        this.ViewModel[LevelOptionsViewModelKey] = levelOptions;

        var syllabus = await this.LevelRepository.GetSyllabusAsync();
        foreach (var level in syllabus.Levels)
        {
            foreach (var subLevel in level.SubLevels)
            {
                var abilities = new List<GamePlayingAbility>(100);

                foreach (var g in subLevel.Games)
                {
                    var gwa = await this.MetricsRepository.GetGamePlayingAbilityAsync(g.Value);
                    abilities.Add(gwa);
                }

                double PlayingScore = AssessmentMetricsProcessor.ComputePlayingLevelAbility(abilities);

                levelOptions.Add(new GameLevelChoiceItem()
                    {
                        LevelAbilityMetric = PlayingScore,
                        AbilityCaption = PlayingScore.ToString(),
                        LevelCaption = subLevel.Name,
                        LevelDescriptor = level.Ordinal + "." + subLevel.Ordinal,
                        LevelLevels = subLevel.Games.Select(g => g.Value),
                    });

                await Task.Delay(100);
            }
        }

Na Windows Store, quando há uma boa transição de tema na coleção, o efeito é bastante desejável.

Lucas

  • Ver comentários. Quando estava escrevendo minha resposta rapidamente, estava pensando no ato de pegar um bloco de código síncrono e, em seguida, devolver o thread a seu chamador, o efeito de tornar o bloco de código assíncrono. Não quero reformular completamente minha resposta, porque então os leitores não poderão ver o que Servy e eu estávamos discutindo.

"agora é possível abandonar o thread da interface do usuário no meio de um bloqueio síncrono" Não, não é. Você acabou de tornar o código assíncrono , em vez de enviar mensagens do thread de IU em um método síncrono. Agora, um aplicativo WPF projetado corretamente seria aquele que nunca bloqueia o thread de IU executando de maneira síncrona operações de longa execução no thread de IU em primeiro lugar, usando assincronia para permitir que a bomba de mensagem existente bombeie mensagens de maneira apropriada.
— Servy

@Servy Nos bastidores, o awaitfará com que o compilador inscreva o restante do método assíncrono como uma continuação da tarefa esperada. Essa continuação ocorrerá no thread de interface do usuário (mesmo contexto de sincronização). O controle então retorna ao chamador do método assíncrono, isto é, subsistema de eventos WPFs, onde os eventos serão executados até que a continuação programada seja executada algum tempo após o período de atraso expirar.
— Luke Puplett

sim, estou bem ciente disso. Isso é o que torna o método assíncrono (o controle de cedência para o chamador e apenas o agendamento de uma continuação). Sua resposta afirma que o método é síncrono quando na verdade está usando assincronia para atualizar a IU.
— Servy

O primeiro método (o código do OP) é ​​síncrono, Servy. O segundo exemplo é apenas uma dica para manter a IU funcionando quando em um loop ou tendo que colocar itens em uma longa lista.
— Luke Puplett

E o que você fez foi tornar o código síncrono assíncrono. Você não manteve a IU responsiva de dentro de um método síncrono, como afirma sua descrição ou a resposta pede.
— Servy

0

Faça seu DoEvent () no WPF:

Thread t = new Thread(() => {
            // do some thing in thread
            
            for (var i = 0; i < 500; i++)
            {
                Thread.Sleep(10); // in thread

                // call owner thread
                this.Dispatcher.Invoke(() => {
                    MediaItem uc = new MediaItem();
                    wpnList.Children.Add(uc);
                });
            }
            

        });
        t.TrySetApartmentState(ApartmentState.STA); //for using Clipboard in Threading
        t.Start();

Funciona bem para mim!


-2

Respondendo à pergunta original: Onde está DoEvents?

Acho que DoEvents é VBA. E o VBA não parece ter uma função Sleep. Mas o VBA tem uma maneira de obter exatamente o mesmo efeito de um Sleep ou Delay. Parece-me que DoEvents é equivalente a Sleep (0).

Em VB e C #, você está lidando com .NET. E a pergunta original é uma pergunta C #. Em C #, você usaria Thread.Sleep (0), onde 0 é 0 milissegundos.

Você precisa

using System.Threading.Task;

na parte superior do arquivo para usar

Sleep(100);

em seu código.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.