WPF'de Application.DoEvents () nerede?


89

Bir düğmeye her basıldığında yakınlaştıran aşağıdaki örnek koda sahibim:

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

çıktı:

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

Gördüğünüz gibi, kullanarak çözmeyi düşündüğüm bir problem var Application.DoEvents();ama ... .NET 4'te önsel olarak mevcut değil .

Ne yapalım?


8
Diş mi? Application.DoEvents (), fakir adamın düzgün çok iş parçacıklı uygulamaları yazmak için yerini alması ve her halükarda son derece zayıf uygulama.
Colin Mackay

2
Bunun kötü ve kötü olduğunu biliyorum, ama hiçbir şey olmayan bir şeyi tercih ederim.
serhio

Yanıtlar:


25

Eski Application.DoEvents () yöntemi, WPF'de , açıkladığınız gibi işlemeyi yapmak için Dağıtıcı veya Arka Plan İş Parçacığı kullanmak lehine kullanımdan kaldırılmıştır . Her iki nesnenin de nasıl kullanılacağıyla ilgili birkaç makalenin bağlantılarına bakın.

Application.DoEvents () öğesini kesinlikle kullanmanız gerekiyorsa, system.windows.forms.dll dosyasını uygulamanıza aktarabilir ve yöntemi çağırabilirsiniz. Ancak, WPF'nin sağladığı tüm avantajları kaybettiğiniz için bu gerçekten tavsiye edilmez.


1
Bunun kötü ve kötü olduğunu biliyorum ama hiçbir şey olmayan bir şeyi tercih ederim ... Durumumda Dispatcher'ı nasıl kullanabilirim?
serhio

3
Durumunu anlıyorum. İlk WPF uygulamamı yazdığımda içindeydim, ancak devam ettim ve yeni kütüphaneyi öğrenmek için zaman ayırdım ve uzun vadede bunun için çok daha iyiydi. Zaman ayırmanızı şiddetle tavsiye ederim. Özel durumunuza gelince, bana göre, tıklama olayınız her tetiklendiğinde sevk görevlisinin koordinatların görüntülenmesini işlemesini isteyeceksiniz. Kesin uygulama için Dispatcher hakkında daha fazla bilgi edinmeniz gerekir.
Dillie-O

13
Application.DoEvents () 'i kaldırmak, neredeyse MS'in Windows 8'de "Başlat" düğmesini kaldırması kadar can sıkıcı.
JeffHeaton

2
Hayır, bu iyi bir şeydi, bu yöntem yarardan çok zarara neden oldu.
Jesse

1
Bunun günümüzde ve çağda hala bir sorun olduğuna inanamıyorum. Zaman uyumsuz davranışı simüle etmek için VB6'da DoEvents'i çağırmam gerektiğini hatırlıyorum. Sorun şu ki, bir arka plan çalışanı yalnızca UI işleme yapmadığınızda çalışıyor, ancak UI işleme - örneğin binlerce ListBoxItems oluşturmak UI iş parçacığını kilitleyebilir. Yapılması gereken gerçekten ağır kullanıcı arabirimi işi olduğunda ne yapmamız gerekiyor?
Christian Findlay

136

Bunun gibi bir şey dene

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

1
Uygulama için bir uzatma yöntemi bile yazdım :)public static void DoEvents(this Application a)
serhio

@serhio: Düzgün uzatma yöntemi :)
Fredrik Hedblad

3
Bununla birlikte, gerçek uygulamada Application.Currentbazen boş olduğuna dikkat etmeliyim ... bu yüzden belki de tam olarak eşdeğer değildir.
serhio

Mükemmel. Cevap bu olmalı. Naysayers övgü almamalı.
Jeson Martajaya

7
Kesilen bir talimat yapılırsa (yani, bir senkronizasyonda bu komuta devam eden bir WCF yöntemine çağrı), çerçeveyi itmediği için bu her zaman çalışmayacaktır, engelleneceği için muhtemelen 'yenileme' görmeyeceksiniz. .. Bu nedenle MSDN kaynağından sağlanan flq yanıtı bundan daha doğrudur.
GY

58

Pekala, Dispatcher iş parçacığında çalışan bir yöntem üzerinde çalışmaya başladığım ve UI iş parçacığını engellemeden engellemesi gereken bir duruma çarptım. Msdn'nin, Dispatcher'ın kendisine bağlı olarak DoEvents () 'in nasıl uygulanacağını açıkladığı ortaya çıktı:

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

( Dispatcher.PushFrame Yönteminden alınmıştır )

Bazıları, aynı mantığı uygulayacak tek bir yöntemde tercih edebilir:

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

Güzel bul! Bu, Meleak tarafından önerilen uygulamadan daha güvenli görünüyor. Bununla ilgili bir blog yazısı
HugoRune

2
@HugoRune Bu blog yazısı, bu yaklaşımın gereksiz olduğunu ve Meleak ile aynı uygulamayı kullandığını belirtiyor.
Lukazoid

1
@Lukazoid Anlayabildiğim kadarıyla basit uygulama, izlenmesi zor kilitlenmelere neden olabilir. (Nedeni konusunda emin değilim, muhtemelen sorun, dağıtım görevlisi kuyruğundaki kodun DoEvents'i tekrar çağırması veya dağıtım görevlisi sırasındaki kodun başka dağıtım çerçevesi oluşturmasıdır.) Her durumda, exitFrame ile çözüm böyle bir sorun sergilemedi, bu yüzden bunu tavsiye ederim. (Ya da elbette, doEvents'i hiç kullanmamak)
HugoRune

1
Caliburn'un uygulama kapanırken VM'leri dahil etme yöntemiyle birlikte bir iletişim kutusu yerine pencerenizde bir yer paylaşımı gösterilmesi geri aramaları dışladı ve engellemeden engellememizi gerektirdi. DoEvents hack olmadan bana bir çözüm sunarsanız çok memnun olurum.
flq

1
eski blog gönderisine yeni bağlantı: kent-boogaart.com/blog/dispatcher-frames
CAD bloke

13

Sadece pencere grafiğini güncellemeniz gerekiyorsa, bunun gibi kullansanız iyi olur

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

1
DispatcherPriority.Render'ı kullanmak DispatcherPriority.Background'dan daha hızlı çalışır. Bugün StopWatcher ile test edildi
Александр Пекшев

Ben sadece bu numarayı kullandım ama en iyi sonuçlar için DispatcherPriority.Send (en yüksek öncelik) kullanıyorum; daha duyarlı kullanıcı arayüzü.
Bent Rasmussen

6
myCanvas.UpdateLayout();

işe yarıyor gibi görünüyor.


Bunu benim için çok daha güvenli göründüğü için kullanacağım, ancak diğer durumlar için DoEvents'i saklayacağım.
Carter Medlin

Neden bilmiyorum ama bu benim için çalışmıyor. DoEvents () iyi çalışıyor.
newman

Benim durumumda, bunu DoEvents () gibi yapmak zorundaydım
Jeff

3

Önerilen her iki yaklaşımla ilgili bir sorun, boşta CPU kullanımını gerektirmeleridir (benim deneyimime göre% 12'ye kadar). Bu, bazı durumlarda, örneğin modal UI davranışı bu teknik kullanılarak uygulandığında yetersizdir.

Aşağıdaki varyasyon, bir zamanlayıcı kullanan kareler arasında minimum bir gecikme sağlar (burada Rx ile yazıldığını ancak herhangi bir normal zamanlayıcıyla elde edilebileceğini unutmayın):

 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

Kullanılmaya başlandığından asyncve awaitartık UI iş parçacığının (önceden) * eşzamanlı bir kod bloğu boyunca Task.Delay, örn.

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

Dürüst olacağım, yukarıdaki kodu tam olarak denemedim, ancak ItemsControlpahalı bir öğe şablonuna sahip bir ürüne birçok öğe yerleştirdiğimde , bazen diğerini vermek için küçük bir gecikme eklediğimde onu sıkı döngülerde kullanıyorum kullanıcı arayüzünde daha fazla zaman.

Örneğin:

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

Windows Mağazasında, koleksiyonda güzel bir tema geçişi olduğunda, efekt oldukça arzu edilir.

Luke

  • Yorumlara bakınız. Cevabımı hızlı bir şekilde yazarken, eşzamanlı bir kod bloğu alma ve ardından iş parçacığını arayana geri bırakma eylemini düşünüyordum, bunun etkisi kod bloğunu eşzamansız hale getiriyor. Cevabımı tamamen yeniden ifade etmek istemiyorum çünkü o zaman okuyucular Servy ve benim ne hakkında tartıştığımızı göremezler.

"Artık UI iş parçacığını eşzamanlı bir engelleme yoluyla kısmen bırakmak mümkündür" Hayır, değil. Sadece ettik kod uyumsuz yapılmış oldukça senkron yöntemde UI iş parçacığı iletileri pompalama yerine,. Şimdi, doğru tasarlanmış bir WPF uygulaması, mevcut mesaj pompasının mesajları uygun şekilde pompalamasına izin vermek için zaman uyumsuzluğu kullanarak, UI iş parçacığında uzun süre çalışan işlemleri eşzamanlı olarak gerçekleştirerek UI iş parçacığını asla engellemeyen bir uygulama olacaktır.
2014

@Servy Kapakların altında, awaitderleyicinin geri kalan eşzamansız yöntemi beklenen görevin devamı olarak kaydetmesine neden olur. Bu devam, UI iş parçacığında gerçekleşecektir (aynı senkronizasyon bağlamı). Kontrol daha sonra eşzamansız yöntemin çağıranına, yani WPF olayları alt sistemine geri döner, burada olaylar, zamanlanan devamlılık, gecikme süresinin sona ermesinden bir süre sonra çalıştırılıncaya kadar çalıştırılır.
Luke Puplett

evet, bunun farkındayım. Yöntemi eşzamansız yapan da budur (arayan kişiye kontrol sağlamak ve yalnızca bir devamın planlanması). Cevabınız , kullanıcı arayüzünü güncellemek için asenkroni kullandığında yöntemin senkronize olduğunu belirtir .
2014 18

İlk yöntem (OP'nin kodu) senkronize, Servy'dir. İkinci örnek, kullanıcı arayüzünün bir döngüdeyken veya öğeleri uzun bir listeye dökmek zorunda kaldığında devam etmesini sağlamak için bir ipucudur.
Luke Puplett

Ve yaptığınız şey, eşzamanlı kodu eşzamansız yapmak oldu. Açıklamanızın belirttiği veya cevabın istediği gibi, kullanıcı arayüzünü senkronize bir yöntem içinden duyarlı tutmadınız.
2014

0

WPF'de DoEvent'inizi () yapın:

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

Benim için iyi çalış!


-2

Orijinal soruyu cevaplamak: DoEvents nerede?

Sanırım DoEvents VBA. Ve VBA bir Uyku işlevine sahip görünmüyor. Ancak VBA'nın Uyku veya Gecikme ile tam olarak aynı etkiyi elde etmenin bir yolu vardır. Bana DoEvents'in Sleep (0) ile eşdeğer olduğu anlaşılıyor.

VB ve C # 'da .NET ile uğraşıyorsunuz. Ve asıl soru bir C # sorusudur. C # 'da, 0'ın 0 milisaniye olduğu Thread.Sleep (0) kullanırsınız.

İhtiyacın var

using System.Threading.Task;

kullanmak için dosyanın en üstünde

Sleep(100);

kodunuzda.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.