Dov'è Application.DoEvents () in WPF?


88

Ho il seguente codice di esempio che ingrandisce ogni volta che viene premuto un pulsante:

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

l'output è:

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

come puoi vedere c'è un problema che pensavo risolvere usando Application.DoEvents();ma ... non esiste a priori in .NET 4.

Cosa fare?


8
Filettatura? Application.DoEvents () era il sostituto del povero per scrivere correttamente applicazioni multi-thread e pratica estremamente povera in ogni caso.
Colin Mackay

2
So che è povero e cattivo, ma preferisco qualcosa che niente.
serhio

Risposte:


25

Il vecchio metodo Application.DoEvents () è stato deprecato in WPF a favore dell'utilizzo di un Dispatcher o di un thread di lavoro in background per eseguire l'elaborazione come descritto. Vedere i collegamenti per un paio di articoli su come utilizzare entrambi gli oggetti.

Se è assolutamente necessario utilizzare Application.DoEvents (), è possibile importare semplicemente system.windows.forms.dll nella propria applicazione e chiamare il metodo. Tuttavia, questo in realtà non è consigliato, poiché stai perdendo tutti i vantaggi offerti da WPF.


So che è cattivo e cattivo, ma preferisco qualcosa che niente ... Come posso usare Dispatcher nella mia situazione?
serhio

3
Capisco la tua situazione. Ero coinvolto quando ho scritto la mia prima app WPF, ma sono andato avanti e mi sono preso il tempo per imparare la nuova libreria ed è stato molto meglio per questo a lungo termine. Consiglio vivamente di dedicare del tempo. Per quanto riguarda il tuo caso particolare, mi sembra che desideri che il dispatcher gestisca la visualizzazione delle coordinate ogni volta che viene attivato l'evento clic. Avresti bisogno di leggere di più sul Dispatcher per l'esatta implementazione.
Dillie-O

no, chiamerei Application.DoEventsdopo aver incrementato myScaleTransform.ScaleX. Non so se è possibile con Dispatcher.
serhio

12
Rimuovere Application.DoEvents () è fastidioso quasi quanto MS rimuovere il pulsante "Start" su Windows 8.
JeffHeaton

2
No, è stata una buona cosa, quel metodo ha causato più danni che benefici.
Jesse

136

Prova qualcosa di simile

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

1
Ho anche scritto un metodo di estensione per l'applicazione :)public static void DoEvents(this Application a)
serhio

@serhio: Neat extension method :)
Fredrik Hedblad

2
Devo sottolineare tuttavia che nell'applicazione reale a Application.Currentvolte è nullo ... quindi forse non è del tutto equivalente.
serhio

Perfetto. Questa dovrebbe essere la risposta. Gli oppositori non dovrebbero ottenere il merito.
Jeson Martajaya

6
Questo non funzionerà sempre in quanto non spinge il frame, se viene eseguita un'istruzione di interruzione (cioè una chiamata a un metodo WCF che in modo sincronico continua a questo comando) è probabile che non vedrai `` aggiorna '' poiché verrà bloccato .. Questo è il motivo per cui la risposta flq fornita dalla risorsa MSDN è più corretta di questa.
GY

57

Bene, ho appena raggiunto un caso in cui inizio a lavorare su un metodo che viene eseguito sul thread Dispatcher e deve essere bloccato senza bloccare il thread dell'interfaccia utente. Si scopre che msdn spiega come implementare un DoEvents () basato sul Dispatcher stesso:

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

(tratto dal metodo Dispatcher.PushFrame )

Alcuni potrebbero preferirlo in un unico metodo che applicherà la stessa logica:

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

Bella scoperta! Questo sembra più sicuro dell'implementazione suggerita da Meleak. Ho trovato un post sul blog a riguardo
HugoRune

2
@HugoRune Questo post sul blog afferma che questo approccio non è necessario e che utilizza la stessa implementazione di Meleak.
Lukazoid

1
@Lukazoid Per quanto ne so, la semplice implementazione può causare blocchi difficili da rintracciare. (Non sono sicuro della causa, forse il problema è il codice nella coda del dispatcher che chiama di nuovo DoEvents, o il codice nella coda del dispatcher che genera ulteriori frame del dispatcher.) In ogni caso, la soluzione con exitFrame non ha mostrato tali problemi, quindi avrei consiglia quello. (O, ovviamente, non usare affatto doEvents)
HugoRune

1
La visualizzazione di una sovrapposizione sulla finestra invece di una finestra di dialogo in combinazione con il modo in cui caliburn coinvolge le VM quando l'app è chiusa ha escluso i callback e ci ha richiesto di bloccare senza bloccare. Sarei felice se mi presentassi una soluzione senza l'hack DoEvents.
flq

1
nuovo collegamento al vecchio post del blog: kent-boogaart.com/blog/dispatcher-frames
CAD bloke

11

Se hai bisogno di aggiornare solo la grafica della finestra, meglio usarla in questo modo

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

L'uso di DispatcherPriority.Render funziona più velocemente di DispatcherPriority.Background. Testato oggi con StopWatcher
Александр Пекшев

Ho appena usato questo trucco ma uso DispatcherPriority.Send (priorità più alta) per ottenere i migliori risultati; interfaccia utente più reattiva.
Bent Rasmussen

6
myCanvas.UpdateLayout();

sembra funzionare pure.


Lo userò poiché mi sembra molto più sicuro, ma manterrò i DoEvents per altri casi.
Carter Medlin

Non so perché, ma questo non funziona per me. DoEvents () funziona bene.
newman

Nel mio caso ho dovuto farlo così come i DoEvents ()
Jeff

3

Un problema con entrambi gli approcci proposti è che comportano un utilizzo inattivo della CPU (fino al 12% nella mia esperienza). Ciò non è ottimale in alcuni casi, ad esempio quando il comportamento dell'interfaccia utente modale viene implementato utilizzando questa tecnica.

La seguente variazione introduce un ritardo minimo tra i fotogrammi utilizzando un timer (si noti che è scritto qui con Rx ma può essere ottenuto con qualsiasi timer normale):

 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

Dall'introduzione di asynced è awaitora possibile abbandonare il thread dell'interfaccia utente in parte attraverso un blocco di codice sincrono (precedentemente) * utilizzando Task.Delay, ad es.

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

Sarò onesto, non l'ho provato con il codice esatto sopra, ma lo uso in loop stretti quando inserisco molti elementi in un ItemsControlmodello di oggetto costoso, a volte aggiungendo un piccolo ritardo per dare l'altro cose sull'interfaccia utente più tempo.

Per esempio:

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

Su Windows Store, quando c'è una bella transizione del tema nella raccolta, l'effetto è abbastanza desiderabile.

Luca

  • vedere i commenti. Quando stavo scrivendo rapidamente la mia risposta, stavo pensando all'atto di prendere un blocco di codice sincrono e poi restituire il thread al suo chiamante, il cui effetto rende asincrono il blocco di codice. Non voglio riformulare completamente la mia risposta perché così i lettori non possono vedere su cosa stessimo litigando io e Servy.

"ora è possibile abbandonare il thread dell'interfaccia utente durante un blocco sincrono" No, non lo è. Hai appena reso il codice asincrono , anziché pompare i messaggi dal thread dell'interfaccia utente in un metodo sincrono. Ora, un'applicazione WPF progettata correttamente sarebbe quella che non blocca mai il thread dell'interfaccia utente eseguendo in primo luogo in modo sincrono operazioni a esecuzione prolungata nel thread dell'interfaccia utente, utilizzando l'asincronia per consentire al pump dei messaggi esistente di pompare i messaggi in modo appropriato.
Servizio

@Servy Sotto le coperte, il awaitcompilatore registrerà il resto del metodo asincrono come continuazione dell'attività attesa. Quella continuazione si verificherà nel thread dell'interfaccia utente (stesso contesto di sincronizzazione). Il controllo ritorna quindi al chiamante del metodo asincrono, ovvero il sottosistema di eventi WPF, in cui gli eventi verranno eseguiti fino a quando la continuazione pianificata non viene eseguita dopo la scadenza del periodo di ritardo.
Luke Puplett

sì, lo so bene. Questo è ciò che rende il metodo asincrono (il controllo cedente al chiamante e la pianificazione solo di una continuazione). La tua risposta afferma che il metodo è sincrono quando in realtà utilizza l' asincronia per aggiornare l'interfaccia utente.
Servizio

Il primo metodo (il codice dell'OP) è sincrono, Servy. Il secondo esempio è solo un suggerimento per mantenere attiva l'interfaccia utente durante un ciclo o per dover versare elementi in un lungo elenco.
Luke Puplett

E quello che hai fatto è rendere asincrono il codice sincrono. Non hai mantenuto la risposta dell'interfaccia utente all'interno di un metodo sincrono, come afferma la tua descrizione, o la risposta richiede.
Servizio

0

Crea il tuo DoEvent () in 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();

Funziona bene per me!


-2

Rispondendo alla domanda originale: dov'è DoEvents?

Penso che DoEvents sia VBA. E VBA non sembra avere una funzione Sleep. Ma VBA ha un modo per ottenere esattamente lo stesso effetto di sospensione o ritardo. Mi sembra che DoEvents sia equivalente a Sleep (0).

In VB e C #, hai a che fare con .NET. E la domanda originale è una domanda C #. In C #, useresti Thread.Sleep (0), dove 0 è 0 millisecondi.

Hai bisogno

using System.Threading.Task;

all'inizio del file per poter utilizzare

Sleep(100);

nel codice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.