WPFのApplication.DoEvents()はどこにありますか?


88

ボタンが押されるたびにズームする次のサンプルコードがあります。

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

出力は次のとおりです。

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

ご覧のとおり、問題はあるので、使って解決しようと思っApplication.DoEvents();たのですが… .NET4はアプリオリには存在しません。

何をすべきか?


8
スレッド?Application.DoEvents()は、適切にマルチスレッド化されたアプリケーションを作成するための貧乏人の代わりであり、いずれにしても非常に貧弱な慣習でした。
コリンマッケイ2010

2
私はそれが悪いことと悪いことを知っていますが、私はまったく何もないものを好みます。
serhio 2010

回答:


25

古いApplication.DoEvents()メソッドはWPFで非推奨になり、ディスパッチャーまたはバックグラウンドワーカースレッドを使用して説明したように処理を行うようになりました。両方のオブジェクトの使用方法に関するいくつかの記事のリンクを参照してください。

どうしてもApplication.DoEvents()を使用する必要がある場合は、system.windows.forms.dllをアプリケーションにインポートして、メソッドを呼び出すだけです。ただし、WPFが提供するすべての利点が失われるため、これは実際には推奨されません。


私はそれが悪いことと悪いことを知っていますが、私はまったく何もないものを好みます...私の状況でディスパッチャーを使用するにはどうすればよいですか?
serhio 2010

3
私はあなたの状況を理解しています。最初のWPFアプリを作成したときはその中にいましたが、先に進んで時間をかけて新しいライブラリを学習し、長期的にははるかに優れていました。時間をかけることを強くお勧めします。あなたの特定のケースに関しては、クリックイベントが発生するたびにディスパッチャに座標の表示を処理させたいように思えます。正確な実装については、ディスパッチャの詳細を読む必要があります。
Dillie-O 2010

いいえ、Application.DoEventsmyScaleTransform.ScaleXをインクリメントした後に呼び出します。Dispatcherで可能かどうかわからない。
serhio 2010

12
Application.DoEvents()を削除すると、Windowsの8の「スタート」ボタンを削除するMSと同じくらい迷惑です
JeffHeaton

2
いいえ、それは良いことでした、その方法は良いよりも害をもたらしました。
ジェシー

136

このようなものを試してください

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

1
アプリケーションの拡張メソッドも作成しました:)public static void DoEvents(this Application a)
serhio 2010

@serhio:きちんとした拡張方法:)
Fredrik Hedblad 2010

2
ただし、実際のアプリケーションでApplication.Currentはnullになることがあるので、おそらくまったく同等ではないことに注意してください。
serhio 2010

完璧です。これが答えになるはずです。否定論者は信用を得るべきではありません。
Jeson Martajaya 2013年

6
これはフレームをプッシュしないため、常に機能するとは限りません。中断する命令(つまり、このコマンドを同期的に続行するWCFメソッドの呼び出し)が行われると、ブロックされるため「更新」が表示されない可能性があります。 ..これが、MSDNリソースから提供された回答flqがこれよりも正しい理由です。
GY

57

さて、ディスパッチャースレッドで実行されるメソッドで作業を開始し、UIスレッドをブロックせずにブロックする必要がある場合に遭遇しました。msdnは、ディスパッチャ自体に基づいてDoEvents()を実装する方法を説明していることがわかりました。

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メソッドから取得)

同じロジックを適用する単一の方法でそれを好む人もいるかもしれません。

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

素敵な発見!これは、Meleakによって提案された実装よりも安全に見えます。私はそれについてのブログ投稿
HugoRune 2012

2
@HugoRuneそのブログ投稿では、このアプローチは不要であり、Meleakと同じ実装を使用すると述べています。
ルカゾイド2012年

1
@Lukazoid私が知る限り、単純な実装は追跡が難しいロックアップを引き起こす可能性があります。(原因はわかりません。問題は、DoEventsを再度呼び出すディスパッチャーキューのコード、またはディスパッチャーフレームを生成するディスパッチャーキューのコードです。)いずれの場合も、exitFrameを使用したソリューションではそのような問題は発生しなかったので、その1つをお勧めします。(または、もちろん、doEventsをまったく使用しません)
HugoRune 2012年

1
アプリが閉じているときにVMを関与させるcaliburnの方法と組み合わせて、ダイアログの代わりにウィンドウにオーバーレイを表示すると、コールバックが除外され、ブロックせずにブロックする必要がありました。DoEventsハックなしで解決策を提示していただければ幸いです。
flq 2012年

1
古いブログ投稿への新しいリンク:kent-boogaart.com/blog/dispatcher-frames
CAD bloke 2018年

11

ウィンドウグラフィックを更新するだけでよい場合は、次のように使用することをお勧めします

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

DispatcherPriority.Renderを使用すると、DispatcherPriority.Backgroundよりも高速に動作します。StopWatcherで今日テスト済み
АлександрПекшев

私はこのトリックを使用しましたが、最良の結果を得るにはDispatcherPriority.Send(最高の優先度)を使用します。より応答性の高いUI。
ベントラスムセン

6
myCanvas.UpdateLayout();

同様に機能するようです。


私にははるかに安全だと思われるのでこれを使用しますが、他の場合のためにDoEventsを保持します。
カーターメドリン2013

理由はわかりませんが、これは私にはうまくいきません。DoEvents()は正常に機能します。
ニューマン2013

私の場合はこれだけでなく、DoEvents関数を(しなければならなかった)
ジェフ・

3

提案された両方のアプローチの1つの問題は、アイドル状態のCPU使用率(私の経験では最大12%)を伴うことです。これは、モーダルUIの動作がこの手法を使用して実装されている場合など、場合によっては最適ではありません。

次のバリエーションでは、タイマーを使用してフレーム間に最小の遅延を導入します(ここでは、Rxで記述されていますが、通常のタイマーで実現できます)。

 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

の導入以来、(以前の)*同期ブロックのコードを使用してUIスレッドを途中で放棄することが可能にasyncなりawaitましたTask.Delay、例えば

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

正直に言うと、上記の正確なコードでは試していませんが、多くのアイテムをに配置するときにタイトなループで使用します ItemsControlが、高価なアイテムテンプレートがある、他のアイテムに与えるために小さな遅延を追加することがありますUIにもっと時間をかけます。

例えば:

        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ストアでは、コレクションに素敵なテーマの移行がある場合、その効果は非常に望ましいものです。

ルカ

  • コメントを参照してください。私が答えをすばやく書いていたとき、私はコードの同期ブロックを取得し、スレッドを呼び出し元に戻すという行為について考えていました。その結果、コードのブロックは非同期になります。私の答えを完全に言い換えたくはありません。なぜなら、読者はServyと私が何について口論していたのかを理解できないからです。

「同期ブロックの途中でUIスレッドを放棄できるようになりました」いいえ、できません。あなただけしましたコードを非同期に行われ、むしろ、同期方法でUIスレッドからのメッセージをポンプよりも、。これで、正しく設計されたWPFアプリケーションは、最初にUIスレッドで長時間実行される操作を同期的に実行し、非同期を使用して既存のメッセージポンプがメッセージを適切にポンピングできるようにすることで、UIスレッドをブロックしないアプリケーションになります。
Servy 2014年

@Servy隠れてawait、コンパイラは待機中のタスクの継続として残りのasyncメソッドにサインアップします。その継続はUIスレッドで発生します(同じ同期コンテキスト)。次に、制御はasyncメソッドの呼び出し元、つまりWPFイベントサブシステムに戻ります。このサブシステムでは、遅延期間が終了した後、スケジュールされた継続が実行されるまでイベントが実行されます。
Luke Puplett 2014年

はい、私はそれをよく知っています。これがメソッドを非同期にするものです(呼び出し元に制御を譲り、継続をスケジュールするだけです)。あなたの答えは、実際には非同期を使用してUIを更新しているのに、メソッドは同期的であると述べています。
Servy 2014年

最初の方法(OPのコード)は同期、Servyです。2番目の例は、ループ内にあるとき、またはアイテムを長いリストに注ぐ必要があるときにUIを継続するためのヒントです。
Luke Puplett 2014年

そして、あなたがしたことは、同期コードを非同期にします。説明にあるように、または答えが求めるように、同期メソッド内からUIの応答性を維持していません。
Servy 2014年

0

WPFでDoEvent()を作成します。

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

私のためにうまくいきます!


-2

元の質問への回答:DoEventsはどこにありますか?

DoEventsはVBAだと思います。また、VBAにはスリープ機能がないようです。ただし、VBAには、スリープまたは遅延とまったく同じ効果を得る方法があります。DoEventsはSleep(0)と同等であるように私には思えます。

VBとC#では、.NETを扱っています。そして、元の質問はC#の質問です。C#では、Thread.Sleep(0)を使用します。0は0ミリ秒です。

あなたが必要

using System.Threading.Task;

使用するためにファイルの先頭に

Sleep(100);

あなたのコードで。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.