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


Tengo el siguiente código de muestra que se acerca 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 puedes ver, hay un problema, que pensé resolver usando Application.DoEvents(); pero ... no existe a priori en .NET 4.

¿Qué hacer?


70
2017-12-21 17:18


origen


Respuestas:


El antiguo método Application.DoEvents () ha quedado obsoleto en WPF a favor de usar un Transportista o una Hilo de trabajador de fondo para hacer el procesamiento como lo ha descrito. Vea los enlaces de un par de artículos sobre cómo usar ambos objetos.

Si debe usar Application.DoEvents (), simplemente debe 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 proporciona WPF.


23
2017-12-21 17:25



Pruebe algo como esto

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

111
2017-12-21 17:37



Bueno, acabo de encontrar un caso en el que empiezo a trabajar en un método que se ejecuta en el hilo Dispatcher, y necesita bloquearse sin bloquear el subproceso UI. 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 de Dispatcher.PushFrame Method)


48
2017-08-10 10:04



myCanvas.UpdateLayout();

parece funcionar también.


3
2017-12-21 20:49



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

La siguiente variación introduce un retraso mínimo entre fotogramas usando un temporizador (tenga en cuenta que está escrito aquí con Rx pero puede lograrse 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()));

3
2017-08-05 15:32



Si solo necesita actualizar el gráfico de la ventana, mejor use esto

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

2
2018-03-19 11:43



Desde la introducción de async y await ahora es posible renunciar a la hebra de UI parcialmente a través de un bloque de código (anteriormente) * síncrono usando Task.Delay, p.ej.

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ía honesto, no lo he intentado con el código exacto anterior, pero lo uso en círculos apretados cuando coloco muchos elementos en un ItemsControl que tiene una plantilla de artículo costosa, a veces agregando un pequeño retraso para dar a las demás 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 devolver el hilo a su llamador, cuyo efecto hace que el bloque de código sea asincrónico. No quiero reformular por completo mi respuesta porque los lectores no pueden ver lo que Servy y yo estábamos discutiendo.

1
2017-11-03 18:32