Gdzie jest Application.DoEvents () w WPF?

88

Mam następujący przykładowy kod, który powiększa się po każdym naciśnięciu przycisku:

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

wynik to:

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

jak widać, jest problem, który myślałem, że można go rozwiązać za pomocą, Application.DoEvents();ale ... nie istnieje a priori w .NET 4.

Co robić?

serhio
źródło
8
Gwintowanie? Application.DoEvents () był substytutem dla biedaka do pisania poprawnie wielowątkowych aplikacji i wyjątkowo złej praktyki w każdym przypadku.
Colin Mackay,
2
Wiem, że to słabe i złe, ale wolę coś, co nie ma nic.
serhio

Odpowiedzi:

25

Stara metoda Application.DoEvents () została uznana za przestarzałą w WPF na korzyść używania modułu Dispatcher lub wątku roboczego w tle do przetwarzania zgodnie z opisem. Zobacz linki do kilku artykułów na temat używania obu obiektów.

Jeśli absolutnie musisz użyć Application.DoEvents (), możesz po prostu zaimportować system.windows.forms.dll do swojej aplikacji i wywołać metodę. Jednak to naprawdę nie jest zalecane, ponieważ tracisz wszystkie zalety oferowane przez WPF.

Dillie-O
źródło
Wiem, że to jest kiepskie i złe, ale wolę coś, czego w ogóle nic ... Jak mogę użyć Dyspozytora w mojej sytuacji?
serhio
3
Rozumiem twoją sytuację. Byłem w tym, kiedy pisałem swoją pierwszą aplikację WPF, ale poszedłem do przodu i poświęciłem czas na naukę nowej biblioteki i na dłuższą metę był dla niej znacznie lepszy. Gorąco polecam poświęcenie czasu. Jeśli chodzi o Twój konkretny przypadek, wydaje mi się, że chcesz, aby dyspozytor zajmował się wyświetlaniem współrzędnych za każdym razem, gdy uruchamiane jest zdarzenie kliknięcia. Aby uzyskać dokładną implementację, musisz przeczytać więcej na temat Dispatchera.
Dillie-O
nie, zadzwoniłbym Application.DoEventspo zwiększeniu myScaleTransform.ScaleX. Nie wiem, czy jest to możliwe z Dispatcherem.
serhio
12
Usunięcie Application.DoEvents () jest prawie tak denerwujące, jak usunięcie przez MS przycisku „Start” w systemie Windows 8.
JeffHeaton,
2
Nie, to była dobra rzecz, ta metoda przyniosła więcej szkody niż pożytku.
Jesse
136

Spróbuj czegoś takiego

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Background,
                                          new Action(delegate { }));
}
Fredrik Hedblad
źródło
1
Napisałem nawet metodę rozszerzenia aplikacji :)public static void DoEvents(this Application a)
serhio
@serhio: Schludna metoda rozszerzenia :)
Fredrik Hedblad
2
Powinienem jednak zauważyć, że w prawdziwej aplikacji Application.Currentczasami jest zerowa ... więc być może nie jest całkiem równoważna.
serhio
Idealny. To powinna być odpowiedź. Nayayers nie powinien otrzymać kredytu.
Jeson Martajaya
6
To nie zawsze będzie działać, ponieważ nie przesuwa ramki, jeśli wykonywana jest instrukcja przerywająca (tj. Wywołanie metody WCF, która w synchronizacji kontynuuje to polecenie), prawdopodobnie nie zobaczysz `` odśwież '', ponieważ zostanie zablokowany .. Dlatego odpowiedź flq dostarczona z zasobu MSDN jest bardziej poprawna niż ta.
GY
57

Cóż, właśnie trafiłem na przypadek, w którym zaczynam pracę nad metodą działającą w wątku Dispatchera i musi ona blokować bez blokowania wątku interfejsu użytkownika. Okazuje się, że msdn wyjaśnia, jak zaimplementować DoEvents () na podstawie samego Dispatchera:

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

(pobrane z metody Dispatcher.PushFrame )

Niektórzy mogą preferować to w jednej metodzie, która będzie egzekwować tę samą logikę:

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
źródło
Niezłe znalezisko! Wygląda to na bezpieczniejsze niż sugerowana implementacja przez Meleak. Znalazłem wpis na blogu na ten temat
HugoRune
2
@HugoRune Ten post na blogu stwierdza, że ​​to podejście jest niepotrzebne i że używa tej samej implementacji, co Meleak.
Lukazoid
1
@Lukazoid O ile wiem, prosta implementacja może spowodować trudne do wyśledzenia blokady. (Nie jestem pewien przyczyny, prawdopodobnie problem to kod w kolejce dyspozytora wywołujący ponownie DoEvents lub kod w kolejce programu wysyłającego generujący kolejne ramki wysyłające). W każdym razie rozwiązanie z exitFrame nie wykazywało takich problemów, więc polecam ten. (Lub, oczywiście, w ogóle nie używaj doEvents)
HugoRune
1
Wyświetlanie nakładki w oknie zamiast okna dialogowego w połączeniu ze sposobem Caliburn do angażowania maszyn wirtualnych podczas zamykania aplikacji wykluczyło wywołania zwrotne i wymagało od nas zablokowania bez blokowania. Byłbym zachwycony, gdybyś przedstawił mi rozwiązanie bez włamania DoEvents.
flq
1
nowy link do starego posta na blogu: kent-boogaart.com/blog/dispatcher-frames
bloke CAD
11

Jeśli potrzebujesz tylko zaktualizować grafikę okna, lepiej użyj w ten sposób

public static void DoEvents()
{
    Application.Current.Dispatcher.Invoke(DispatcherPriority.Render,
                                          new Action(delegate { }));
}
Александр Пекшев
źródło
Korzystanie z DispatcherPriority.Render działa szybciej niż DispatcherPriority.Background. Testowany dzisiaj za pomocą StopWatchera
Александр Пекшев
Właśnie użyłem tej sztuczki, ale użyj DispatcherPriority.Send (najwyższy priorytet), aby uzyskać najlepsze wyniki; bardziej responsywny interfejs użytkownika.
Bent Rasmussen
6
myCanvas.UpdateLayout();

wydaje się również działać.

serhio
źródło
Zamierzam tego użyć, ponieważ wydaje mi się to o wiele bezpieczniejsze, ale zachowam DoEvents dla innych przypadków.
Carter Medlin
Nie wiem dlaczego, ale to nie działa dla mnie. DoEvents () działa dobrze.
newman
W moim przypadku musiałem to zrobić, podobnie jak DoEvents ()
Jeff,
3

Jednym z problemów z obydwoma proponowanymi podejściami jest to, że wiążą się one z bezczynnym wykorzystaniem procesora (z mojego doświadczenia do 12%). W niektórych przypadkach jest to nieoptymalne, na przykład gdy zaimplementowano zachowanie modalnego interfejsu użytkownika przy użyciu tej techniki.

Poniższa odmiana wprowadza minimalne opóźnienie między klatkami przy użyciu timera (zauważ, że jest napisane tutaj z Rx, ale można to osiągnąć za pomocą dowolnego zwykłego timera):

 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
źródło
1

Od czasu wprowadzenia asynci awaitteraz można zrezygnować z wątku interfejsu użytkownika w trakcie (dawniej) * synchronicznego bloku kodu przy użyciu Task.Delaynp.

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

Będę szczery, nie próbowałem tego z dokładnym kodem powyżej, ale używam go w ciasnych pętlach, kiedy umieszczam wiele przedmiotów w ItemsControldrogim szablonie przedmiotu, czasami dodając małe opóźnienie, aby nadać drugiemu rzeczy w interfejsie użytkownika więcej czasu.

Na przykład:

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

W Sklepie Windows, gdy w kolekcji jest ładna zmiana motywu, efekt jest całkiem pożądany.

Łukasz

  • Zobacz komentarze. Kiedy szybko pisałem odpowiedź, zastanawiałem się nad aktem pobrania synchronicznego bloku kodu, a następnie oddania wątku z powrotem do jego wywołującego, co powoduje, że blok kodu jest asynchroniczny. Nie chcę całkowicie przeformułowywać swojej odpowiedzi, ponieważ wtedy czytelnicy nie widzą, o co się kłóciliśmy z Servy.
Luke Puplett
źródło
„teraz można zrezygnować z wątku interfejsu użytkownika w trakcie blokowania synchronicznego”. Nie, nie jest. Właśnie utworzyłeś kod asynchroniczny , zamiast pompować komunikaty z wątku interfejsu użytkownika metodą synchroniczną. Teraz poprawnie zaprojektowana aplikacja WPF nigdy nie blokuje wątku interfejsu użytkownika przez synchroniczne wykonywanie długotrwałych operacji w wątku interfejsu użytkownika, przy użyciu asynchronii, aby umożliwić istniejącej pompie komunikatów odpowiednie pompowanie komunikatów.
Servy
@Servy Pod okładkami awaitspowoduje, że kompilator zarejestruje resztę metody asynchronicznej jako kontynuację oczekiwanego zadania. Ta kontynuacja nastąpi w wątku interfejsu użytkownika (ten sam kontekst synchronizacji). Następnie formant powraca do obiektu wywołującego metody asynchronicznej, tj. Podsystemu zdarzeń WPF, w którym zdarzenia będą uruchamiane do momentu, gdy zaplanowana kontynuacja zostanie uruchomiona po upływie określonego czasu opóźnienia.
Luke Puplett
tak, dobrze o tym wiem. To właśnie sprawia, że ​​metoda jest asynchroniczna (daje kontrolę do obiektu wywołującego i tylko planuje kontynuację). Twoja odpowiedź stwierdza, że ​​metoda jest synchroniczna, podczas gdy w rzeczywistości używa asynchronii do aktualizacji interfejsu użytkownika.
Servy
Pierwsza metoda (kod OP) jest synchroniczna, Servy. Drugi przykład to tylko wskazówka, jak utrzymać interfejs użytkownika w pętli lub w przypadku konieczności wlewania elementów do długiej listy.
Luke Puplett
To, co zrobiłeś, sprawiło, że kod synchroniczny stał się asynchroniczny. Nie utrzymałeś responsywności interfejsu użytkownika z poziomu metody synchronicznej, jak stwierdza twój opis lub prosi o odpowiedź.
Servy
0

Utwórz DoEvent () w 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();

Pracuj dobrze dla mnie!

VinaCaptcha
źródło
-2

Odpowiadając na pierwotne pytanie: gdzie jest DoEvents?

Myślę, że DoEvents to VBA. Wydaje się, że VBA nie ma funkcji uśpienia. Ale VBA ma sposób na uzyskanie dokładnie tego samego efektu, co uśpienie lub opóźnienie. Wydaje mi się, że DoEvents jest odpowiednikiem Sleep (0).

W VB i C # masz do czynienia z .NET. Pierwotne pytanie to pytanie w języku C #. W C # użyłbyś Thread.Sleep (0), gdzie 0 to 0 milisekund.

Potrzebujesz

using System.Threading.Task;

u góry pliku, aby użyć

Sleep(100);

w swoim kodzie.

Nie wiem
źródło