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?

serio
fonte
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.

Dillie-O
fonte
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 { }));
}
Fredrik Hedblad
fonte
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);
}
flq
fonte
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 { }));
}
Александр Пекшев
fonte
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.

serio
fonte
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()));
Jonas Chapuis
fonte
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.
Luke Puplett
fonte
"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!

VinaCaptcha
fonte
-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.

Indinfer
fonte