¿Dónde está Application.DoEvents () en WPF?

88

Tengo el siguiente código de muestra que hace zoom cada vez que se presiona un botón:

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));
    }
}

la salida es:

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 ves, hay un problema, que pensé resolver usando Application.DoEvents();pero ... no existe a priori en .NET 4.

¿Qué hacer?

serhio
fuente
8
Enhebrar? Application.DoEvents () era el sustituto de los pobres para escribir correctamente aplicaciones de subprocesos múltiples y una práctica extremadamente pobre en cualquier caso.
Colin Mackay
2
Sé que es pobre y malo, pero prefiero algo que nada en absoluto.
serhio

Respuestas:

25

El antiguo método Application.DoEvents () ha quedado obsoleto en WPF a favor del uso de un Dispatcher o un Subproceso de trabajo en segundo plano para realizar el procesamiento como ha descrito. Consulte los enlaces para ver un par de artículos sobre cómo utilizar ambos objetos.

Si es absolutamente necesario utilizar Application.DoEvents (), entonces simplemente puede importar system.windows.forms.dll a su aplicación y llamar al método. Sin embargo, esto realmente no es recomendable, ya que está perdiendo todas las ventajas que ofrece WPF.

Dillie-O
fuente
Sé que es pobre y malo, pero prefiero algo que nada en absoluto ... ¿Cómo puedo usar Dispatcher en mi situación?
serhio
3
Entiendo tu situación. Estaba en él cuando escribí mi primera aplicación WPF, pero seguí adelante y me tomé el tiempo para aprender la nueva biblioteca y, a la larga, fui mucho mejor. Recomiendo encarecidamente tomarse el tiempo. En cuanto a su caso particular, me parece que desea que el despachador maneje la visualización de las coordenadas cada vez que se activa el evento de clic. Debería leer más sobre Dispatcher para conocer la implementación exacta.
Dillie-O
no, llamaría Application.DoEventsdespués de incrementar myScaleTransform.ScaleX. No sé si es posible con Dispatcher.
serhio
12
Eliminar Application.DoEvents () es casi tan molesto como MS eliminar el botón "Inicio" en Windows 8.
JeffHeaton
2
No, fue algo bueno, ese método causó más daño que bien.
Jesse
136

Prueba algo como esto

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}
Fredrik Hedblad
fuente
1
Incluso escribí un método de extensión para la aplicación :)public static void DoEvents(this Application a)
serhio
@serhio: Método de extensión ordenado :)
Fredrik Hedblad
2
Sin embargo, debo señalar que en la aplicación real a Application.Currentveces es nulo ... por lo que quizás no sea del todo equivalente.
serhio
Perfecto. Esta debería ser la respuesta. Los detractores no deberían recibir el crédito.
Jeson Martajaya
6
Esto no siempre funcionará ya que no empuja el marco, si se realiza una instrucción de interrupción (es decir, una llamada a un método WCF que en un sincronismo continúa con este comando) es probable que no vea 'actualizar' ya que se bloqueará .. Es por eso que la respuesta flq proporcionada desde el recurso MSDN es más correcta que esta.
GY
57

Bueno, acabo de llegar a un caso en el que empiezo a trabajar en un método que se ejecuta en el subproceso de Dispatcher, y debe bloquearse sin bloquear el subproceso de la interfaz de usuario. Resulta que msdn explica cómo implementar un DoEvents () basado en el propio 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;
}

(tomado del método Dispatcher.PushFrame )

Algunos pueden preferirlo en un solo método que aplicará la misma 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
fuente
¡Buen hallazgo! Esto parece más seguro que la implementación sugerida por Meleak. Encontré una publicación de blog al respecto
HugoRune
2
@HugoRune Esa publicación de blog afirma que este enfoque es innecesario y que debe usar la misma implementación que Meleak.
Lukazoid
1
@Lukazoid Por lo que puedo decir, la implementación simple puede causar bloqueos difíciles de rastrear. (No estoy seguro de la causa, posiblemente el problema sea el código en la cola del despachador que llama a DoEvents nuevamente, o el código en la cola del despachador que genera más tramas del despachador). En cualquier caso, la solución con exitFrame no presentó tales problemas, así que recomiendo ese. (O, por supuesto, no usar doEvents en absoluto)
HugoRune
1
Mostrar una superposición en su ventana en lugar de un cuadro de diálogo en combinación con la forma de caliburn de involucrar a las VM cuando la aplicación se está cerrando descartó las devoluciones de llamada y requirió que bloqueáramos sin bloquear. Estaría encantado si me presentara una solución sin el hack de DoEvents.
flq
1
nuevo enlace a la publicación de blog anterior: kent-boogaart.com/blog/dispatcher-frames
CAD
11

Si solo necesita actualizar el gráfico de la ventana, mejor utilícelo así

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}
Александр Пекшев
fuente
El uso de DispatcherPriority.Render funciona más rápido que DispatcherPriority.Background. Probado hoy con StopWatcher
Александр Пекшев
Acabo de usar este truco, pero uso DispatcherPriority.Send (prioridad más alta) para obtener mejores resultados; interfaz de usuario más receptiva.
Bent Rasmussen
6
myCanvas.UpdateLayout();

parece funcionar también.

serhio
fuente
Voy a usar esto porque me parece mucho más seguro, pero me quedaré con los DoEvents para otros casos.
Carter Medlin
No sé por qué, pero esto no me funciona. DoEvents () funciona bien.
Newman
En mi caso, tuve que hacer esto, así como los DoEvents ()
Jeff
3

Un problema con ambos enfoques propuestos es que implican un uso inactivo de la CPU (hasta un 12% en mi experiencia). Esto es subóptimo en algunos casos, por ejemplo, cuando el comportamiento de la interfaz de usuario modal se implementa utilizando esta técnica.

La siguiente variación introduce un retraso mínimo entre fotogramas utilizando un temporizador (tenga en cuenta que está escrito aquí con Rx pero se puede lograr con cualquier 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
fuente
1

Desde la introducción de asyncy awaitahora es posible renunciar al subproceso de la interfaz de usuario a la mitad de un (anteriormente) * bloque de código síncrono utilizando Task.Delay,

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()));
}

Seré honesto, no lo he probado con el código exacto anterior, pero lo uso en bucles estrechos cuando coloco muchos elementos en una ItemsControlplantilla de elemento costosa, a veces agregando un pequeño retraso para dar al otro cosas en la interfaz de usuario más tiempo.

Por ejemplo:

        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);
            }
        }

En Windows Store, cuando hay una buena transición de tema en la colección, el efecto es bastante deseable.

Luke

  • ver comentarios. Cuando estaba escribiendo rápidamente mi respuesta, estaba pensando en el acto de tomar un bloque de código sincrónico y luego entregar el hilo a su llamador, cuyo efecto hace que el bloque de código sea asincrónico. No quiero reformular completamente mi respuesta porque entonces los lectores no pueden ver de qué estamos discutiendo Servy y yo.
Luke Puplett
fuente
"ahora es posible renunciar al subproceso de la interfaz de usuario a la mitad de un bloque sincrónico" No, no lo es. Acaba de hacer que el código sea asincrónico , en lugar de bombear mensajes desde el hilo de la interfaz de usuario en un método sincrónico. Ahora, una aplicación WPF diseñada correctamente sería aquella que nunca bloquea el subproceso de la interfaz de usuario ejecutando de forma sincrónica operaciones de ejecución prolongada en el subproceso de la interfaz de usuario en primer lugar, utilizando la asincronía para permitir que la bomba de mensajes existente bombee mensajes de manera adecuada.
Servicio
@Servy Bajo las sábanas, awaithará que el compilador registre el resto del método asíncrono como una continuación de la tarea esperada. Esa continuación ocurrirá en el hilo de la interfaz de usuario (mismo contexto de sincronización). El control luego regresa al llamador del método asíncrono, es decir, el subsistema de eventos WPF, donde los eventos se ejecutarán hasta que la continuación programada se ejecute en algún momento después de que expire el período de retraso.
Luke Puplett
sí, soy muy consciente de eso. Eso es lo que hace que el método sea asincrónico (ceder el control a la persona que llama y solo programar una continuación). Su respuesta indica que el método es sincrónico cuando, de hecho, usa asincronía para actualizar la interfaz de usuario.
Servicio
El primer método (el código del OP) es sincrónico, Servy. El segundo ejemplo es solo un consejo para mantener la interfaz de usuario en funcionamiento cuando está en un bucle o tiene que agregar elementos a una lista larga.
Luke Puplett
Y lo que ha hecho es convertir el código síncrono en asíncrono. No ha mantenido la interfaz de usuario receptiva desde un método síncrono, como indica su descripción o la respuesta lo solicita.
Servicio
0

Haga su DoEvent () en 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 bien para mí!

VinaCaptcha
fuente
-2

Respondiendo a la pregunta original: ¿Dónde está DoEvents?

Creo que DoEvents es VBA. Y VBA no parece tener una función de suspensión. Pero VBA tiene una forma de obtener exactamente el mismo efecto que una suspensión o un retraso. Me parece que DoEvents es equivalente a Sleep (0).

En VB y C #, se trata de .NET. Y la pregunta original es una pregunta de C #. En C #, usaría Thread.Sleep (0), donde 0 es 0 milisegundos.

Necesitas

using System.Threading.Task;

en la parte superior del archivo para usar

Sleep(100);

en su código.

Indiferente
fuente