web-dev-qa-db-ja.com

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

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

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();を使用して解決すると思った問題がありますが、... .NET 4では存在しませんapriori.

何をすべきか?

79
serhio

Dispatcher または Background Worker Thread を使用して説明したように、古いApplication.DoEvents()メソッドは非推奨になりました。両方のオブジェクトの使用方法に関するいくつかの記事のリンクを参照してください。

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

25
Dillie-O

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

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

さて、私はDispatcherスレッドで実行されるメソッドで作業を開始し、UIスレッドをブロックせずにブロックする必要がある場合にヒットしました。 msdnは、Dispatcher自体に基づいて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 Method から取得)

53
flq

ウィンドウのグラフィックを更新するだけなら、次のように使いましょう

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

同様に動作するようです。

5
serhio

提案された両方のアプローチの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()));
3
Jonas Chapuis

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

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と私が口論したことを見ることができないので、私は私の答えを完全に言い換えたくありません。
1
Luke Puplett