Di mana Application.DoEvents () di WPF?


89

Saya memiliki kode contoh berikut yang memperbesar setiap kali tombol ditekan:

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

outputnya adalah:

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

seperti yang Anda lihat, ada masalah, yang saya pikir bisa diselesaikan dengan menggunakan Application.DoEvents();tapi ... tidak ada apriori di .NET 4.

Apa yang harus dilakukan?


8
Threading? Application.DoEvents () adalah pengganti orang miskin untuk menulis aplikasi multi-thread dengan benar dan praktik yang sangat buruk dalam hal apa pun.
Colin Mackay

2
Saya tahu itu buruk dan buruk, tapi saya lebih suka sesuatu yang tidak ada sama sekali.
serhio

Jawaban:


25

Metode Application.DoEvents () yang lama sudah tidak digunakan lagi di WPF karena menggunakan Dispatcher atau Background Worker Thread untuk melakukan pemrosesan seperti yang telah Anda jelaskan. Lihat tautan untuk beberapa artikel tentang cara menggunakan kedua objek.

Jika Anda benar-benar harus menggunakan Application.DoEvents (), Anda cukup mengimpor system.windows.forms.dll ke dalam aplikasi Anda dan memanggil metode tersebut. Namun, ini sebenarnya tidak disarankan, karena Anda kehilangan semua keuntungan yang disediakan WPF.


1
Saya tahu itu buruk dan buruk, tapi saya lebih suka sesuatu yang tidak ada sama sekali ... Bagaimana saya bisa menggunakan Dispatcher dalam situasi saya?
serhio

3
Aku mengerti situasi mu. Saya berada di dalamnya ketika saya menulis aplikasi WPF pertama saya, tetapi saya melanjutkan dan meluangkan waktu untuk mempelajari perpustakaan baru dan jauh lebih baik untuk itu dalam jangka panjang. Saya sangat merekomendasikan meluangkan waktu. Untuk kasus khusus Anda, menurut saya Anda ingin petugas operator menangani tampilan koordinat setiap kali peristiwa klik Anda diaktifkan. Anda perlu membaca lebih lanjut tentang Dispatcher untuk implementasi yang tepat.
Dillie-O

13
Menghapus Application.DoEvents () hampir sama menjengkelkannya dengan MS yang menghapus tombol "Start" pada Windows 8.
JeffHeaton

2
Tidak, itu hal yang bagus, metode itu menyebabkan lebih banyak kerugian daripada kebaikan.
Jesse

1
Saya tidak percaya ini masih menjadi masalah di zaman sekarang ini. Saya ingat harus memanggil DoEvents di VB6 untuk mensimulasikan perilaku async. Masalahnya adalah pekerja latar belakang hanya bekerja ketika Anda tidak melakukan pemrosesan UI, tetapi pemrosesan UI - seperti misalnya membuat ribuan ListBoxItems dapat mengunci thread UI. Apa yang harus kami lakukan jika ada pekerjaan UI yang benar-benar kuat yang harus dilakukan?
Christian Findlay

136

Cobalah sesuatu seperti ini

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

1
Saya bahkan menulis metode ekstensi untuk aplikasi :)public static void DoEvents(this Application a)
serhio

@serhio: Metode ekstensi rapi :)
Fredrik Hedblad

3
Namun saya harus berkomentar bahwa dalam aplikasi sebenarnya Application.Currentterkadang nol ... jadi mungkin itu tidak cukup setara.
serhio

Sempurna. Ini harus menjadi jawabannya. Penentang seharusnya tidak mendapatkan pujian.
Jeson Martajaya

7
Ini tidak akan selalu berfungsi karena tidak mendorong frame, jika instruksi interupsi sedang dibuat (Yaitu panggilan ke metode WCF yang secara sinkronis lanjutkan ke perintah ini) kemungkinan Anda tidak akan melihat 'refresh' karena akan diblokir .. Inilah sebabnya mengapa jawaban flq yang disediakan dari sumber MSDN lebih benar daripada yang ini.
GY

58

Nah, saya baru saja menemukan kasus di mana saya mulai mengerjakan metode yang berjalan pada utas Dispatcher, dan perlu memblokir tanpa memblokir UI Thread. Ternyata msdn menjelaskan cara mengimplementasikan DoEvents () berdasarkan Dispatcher itu sendiri:

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

(diambil dari Metode Dispatcher.PushFrame )

Beberapa mungkin lebih suka dalam satu metode yang akan menerapkan logika yang sama:

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

Temuan bagus! Ini terlihat lebih aman daripada implementasi yang disarankan oleh Meleak. Saya menemukan entri blog tentang hal itu
HugoRune

2
@HugoRune Postingan blog tersebut menyatakan bahwa pendekatan ini tidak diperlukan, dan menggunakan implementasi yang sama seperti Meleak.
Lukazoid

1
@Lukazoid Sejauh yang saya tahu, penerapan sederhana dapat menyebabkan penguncian yang sulit dilacak. (Saya tidak yakin tentang penyebabnya, mungkin masalahnya adalah kode dalam antrian dispatcher yang memanggil DoEvents lagi, atau kode dalam antrian dispatcher yang menghasilkan frame dispatcher lebih lanjut.) Bagaimanapun, solusi dengan exitFrame tidak menunjukkan masalah seperti itu, jadi saya akan merekomendasikan yang satu itu. (Atau, tentu saja, tidak menggunakan doEvents sama sekali)
HugoRune

1
Menampilkan overlay di jendela Anda alih-alih dialog yang dikombinasikan dengan cara kalibrasi untuk melibatkan VM saat aplikasi ditutup mengesampingkan callback dan mengharuskan kami untuk memblokir tanpa memblokir. Saya akan senang jika Anda memberi saya solusi tanpa peretasan DoEvents.
flq

1
tautan baru ke entri blog lama: kent-boogaart.com/blog/dispatcher-frames
CAD bloke

13

Jika Anda hanya perlu memperbarui grafik jendela, lebih baik gunakan seperti ini

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

1
Penggunaan DispatcherPriority.Render bekerja lebih cepat daripada DispatcherPriority.Background. Diuji hari ini dengan StopWatcher
Александр Пекшев

Saya hanya menggunakan trik ini tetapi menggunakan DispatcherPriority.Send (prioritas tertinggi) untuk hasil terbaik; UI yang lebih responsif.
Bent Rasmussen

6
myCanvas.UpdateLayout();

sepertinya bekerja dengan baik.


Saya akan menggunakan ini karena tampaknya jauh lebih aman bagi saya, tetapi saya akan menyimpan DoEvents untuk kasus lain.
Carter Medlin

Tidak tahu mengapa tapi ini tidak berhasil untuk saya. DoEvents () berfungsi dengan baik.
Newman

Dalam kasus saya, saya harus melakukan ini serta DoEvents ()
Jeff

3

Satu masalah dengan kedua pendekatan yang diusulkan adalah bahwa mereka memerlukan penggunaan CPU yang menganggur (hingga 12% menurut pengalaman saya). Ini tidak optimal dalam beberapa kasus, misalnya ketika perilaku UI modal diimplementasikan menggunakan teknik ini.

Variasi berikut memperkenalkan penundaan minimum antar frame menggunakan pengatur waktu (perhatikan bahwa ini ditulis di sini dengan Rx tetapi dapat dicapai dengan pengatur waktu reguler apa pun):

 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

Sejak pengenalan asyncdan awaitsekarang dimungkinkan untuk melepaskan thread UI sebagian melalui blok kode sinkron (sebelumnya) * menggunakan Task.Delay, misalnya

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

Jujur saja, saya belum mencobanya dengan kode persis di atas, tetapi saya menggunakannya dalam putaran ketat ketika saya menempatkan banyak item ke dalam ItemsControltemplate item yang mahal, terkadang menambahkan sedikit penundaan untuk memberikan yang lain. lebih banyak waktu di UI.

Sebagai contoh:

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

Di Windows Store, ketika ada transisi tema yang bagus pada koleksi, efeknya cukup diinginkan.

Luke

  • lihat komentar. Ketika saya dengan cepat menulis jawaban saya, saya berpikir tentang tindakan mengambil blok kode sinkron dan kemudian melepaskan utas kembali ke pemanggilnya, yang efeknya membuat blok kode tidak sinkron. Saya tidak ingin sepenuhnya mengubah jawaban saya karena pembaca tidak dapat melihat apa yang Servy dan saya pertengkarkan.

"sekarang mungkin untuk melepaskan thread UI sebagian melalui blok sinkron" Tidak, tidak. Anda baru saja membuat kode asinkron , alih-alih memompa pesan dari thread UI dalam metode sinkron. Sekarang, aplikasi WPF yang dirancang dengan benar akan menjadi salah satu yang tidak pernah memblokir thread UI dengan secara sinkron menjalankan operasi yang berjalan lama di thread UI di tempat pertama, menggunakan asinkron untuk memungkinkan pompa pesan yang ada untuk memompa pesan dengan tepat.
Pelayanan

@Servy Di balik penutup, perintah awaitakan menyebabkan kompilator untuk mendaftarkan metode async lainnya sebagai kelanjutan dari tugas yang ditunggu. Kelanjutan tersebut akan terjadi di thread UI (konteks sinkronisasi yang sama). Kontrol kemudian kembali ke pemanggil dari metode async, yaitu subsistem eventing WPF, di mana event akan berjalan sampai kelanjutan yang dijadwalkan berjalan beberapa saat setelah periode penundaan berakhir.
Luke Puplett

ya, saya sangat menyadari itu. Itulah yang membuat metode asynchronous (kontrol hasil ke pemanggil dan hanya menjadwalkan kelanjutan). Jawaban Anda menyatakan bahwa metode tersebut sinkron padahal sebenarnya digunakan asinkron untuk memperbarui UI.
Pelayanan

Metode pertama (kode OP) adalah sinkron, Layanan. Contoh kedua hanyalah tip untuk menjaga UI tetap berjalan saat berada dalam satu lingkaran atau harus menuangkan item ke dalam daftar yang panjang.
Luke Puplett

Dan apa yang telah Anda lakukan adalah membuat kode sinkron menjadi tidak sinkron. Anda belum membuat UI responsif dari dalam metode sinkron, seperti yang dinyatakan oleh deskripsi Anda, atau jawabannya.
Pelayanan

0

Jadikan DoEvent () Anda di 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();

Bekerja dengan baik untukku!


-2

Menjawab pertanyaan awal: Dimana DoEvents?

Saya pikir DoEvents adalah VBA. Dan VBA tampaknya tidak memiliki fungsi Tidur. Tapi VBA memiliki cara untuk mendapatkan efek yang sama persis dengan Sleep atau Delay. Menurut saya, DoEvents setara dengan Sleep (0).

Di VB dan C #, Anda berurusan dengan .NET. Dan pertanyaan aslinya adalah pertanyaan C #. Di C #, Anda akan menggunakan Thread.Sleep (0), di mana 0 adalah 0 milidetik.

Anda membutuhkan

using System.Threading.Task;

di bagian atas file untuk digunakan

Sleep(100);

dalam kode Anda.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.