5

タイムライン コントロールのヘッダーを描画しています。次のようになります。 ここに画像の説明を入力

1 行あたり 0.01 ミリ秒なので、10 分のタイムラインで 60000 行 + 6000 ラベルの描画を検討しています。これには 10 秒ほどかかります。これを UI スレッドからオフロードしたいと思います。私のコードは現在:

private void drawHeader()
{
  Header.Children.Clear();
  switch (viewLevel)
  {
    case ViewLevel.MilliSeconds100:
        double hWidth = Header.Width;
        this.drawHeaderLines(new TimeSpan(0, 0, 0, 0, 10), 100, 5, hWidth);

        //Was looking into background worker to off load UI

        //backgroundWorker = new BackgroundWorker();

        //backgroundWorker.DoWork += delegate(object sender, DoWorkEventArgs args)
        //                               {
        //                                   this.drawHeaderLines(new TimeSpan(0, 0, 0, 0, 10), 100, 5, hWidth);
        //                               };
        //backgroundWorker.RunWorkerAsync();
        break;
    }
}

private void drawHeaderLines(TimeSpan timeStep, int majorEveryXLine, int distanceBetweenLines, double headerWidth)
{
var currentTime = new TimeSpan(0, 0, 0, 0, 0);
const int everyXLine100 = 10;
double currentX = 0;
var currentLine = 0;
while (currentX < headerWidth)
{
    var l = new Line
                {
                    ToolTip = currentTime.ToString(@"hh\:mm\:ss\.fff"),
                    StrokeThickness = 1,
                    X1 = 0,
                    X2 = 0,
                    Y1 = 30,
                    Y2 = 25
                };
    if (((currentLine % majorEveryXLine) == 0) && currentLine != 0)
    {
        l.StrokeThickness = 2;
        l.Y2 = 15;
        var textBlock = new TextBlock
                            {
                                Text = l.ToolTip.ToString(),
                                FontSize = 8,
                                FontFamily = new FontFamily("Tahoma"),
                                Foreground = new SolidColorBrush(Color.FromRgb(255, 255, 255))
                            };

        Canvas.SetLeft(textBlock, (currentX - 22));
        Canvas.SetTop(textBlock, 0);
        Header.Children.Add(textBlock);
    }

    if ((((currentLine % everyXLine100) == 0) && currentLine != 0)
        && (currentLine % majorEveryXLine) != 0)
    {
        l.Y2 = 20;
        var textBlock = new TextBlock
                            {
                                Text = string.Format(".{0}", TimeSpan.Parse(l.ToolTip.ToString()).Milliseconds),
                                                            FontSize = 8,
                                                            FontFamily = new FontFamily("Tahoma"),
                                                            Foreground = new SolidColorBrush(Color.FromRgb(192, 192, 192))
                            };

        Canvas.SetLeft(textBlock, (currentX - 8));
        Canvas.SetTop(textBlock, 8);
        Header.Children.Add(textBlock);
    }
    l.Stroke = new SolidColorBrush(Color.FromRgb(255, 255, 255));
    Header.Children.Add(l);
    Canvas.SetLeft(l, currentX);

    currentX += distanceBetweenLines;
    currentLine++;
    currentTime += timeStep;
}
}

非UIスレッドでUI要素を作成できないことを除いて、BackgroundWorkerを調べました。

非UIスレッドでdrawHeaderLinesを実行することはまったく可能ですか?

線の描画にデータバインディングを使用できますか? これは UI の応答性に役立ちますか?

データバインディングを使用できると思いますが、スタイリングはおそらく現在のWPF能力を超えています(winformから来て、これらすべてのスタイルオブジェクトが何であるかを学び、それらをバインドしようとしています)。

これを誘惑するための出発点を提供できる人はいますか? または、私が始めるためのチュートリアルをGoogleで検索しますか?

4

3 に答える 3

2

つまり、あなたが言ったように、この作業はすべて UI スレッドで行う必要があります。バックグラウンドスレッドでそれを行うことはできません。

ただし、UI スレッドで多くの UI 変更を行う非常に長いループを実行すると、UI スレッドがブロックされるため、明らかにそれを行うことはできません。

ここで重要なのは、実行していることを多数の小さな作業単位に分割し、これらの小さな作業単位をすべて UI スレッドで実行する必要があるということです。UI スレッドは以前と同じように多くの作業を行っていますが (これらすべてを管理するオーバーヘッドのために、おそらくそれよりも多くの作業を行っています)、他のこと (マウスの移動/クリック イベント、キーの押下など) を実行できます。それらのタスクの合間に。

簡単な例を次に示します。

private void button1_Click(object sender, EventArgs e)
{
    TaskScheduler uiContext = TaskScheduler.FromCurrentSynchronizationContext();
    Task.Run(async () =>
    {
        for (int i = 0; i < 1000; i++)
        {
            await Task.Factory.StartNew(() =>
            {
                Controls.Add(new Label() { Text = i.ToString() });
            }, CancellationToken.None, TaskCreationOptions.None, uiContext);
        }
    });
}

最初に UI スレッド内で UI コンテキストを取得し、次にバックグラウンドで新しいスレッドを開始します。このスレッドは、すべての小さなタスクの開始を担当します。ここにループの開始点があります。(あなたのメソッドは単純ではないので、これを別のメソッドに抽出したいかもしれません。) 次に、ループのすぐ内側で新しいタスクを開始し、そのタスクを UI のコンテキストで開始します。そのタスク内に、ループ内にあったものの全体を配置できます。そのタスクをawaiting することで、それぞれが前のタスクの継続としてスケジュールされていることを確認し、すべてが順番に実行されるようにします。順序が重要でない場合 (可能性は低いですが可能です)、待機する必要はまったくありません。

これの C# 4.0 バージョンが必要な場合は、1 つTaskをループから除外し、反復ごとに新しいタスクを前のタスクの継続として接続し、「それ自体」をそのタスクとして設定します。それはもっと厄介でしょう。

于 2012-11-09T20:07:03.787 に答える
0

これは単なる概念実証ですが、ListView 仮想化を使用できると思います。テキストではなく、画像のコレクション (10 行ごとに 1 つの画像) があります。仮想化では、表示されているものだけをレンダリングします。

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <ListView Grid.Row="0" Grid.Column="0" ItemsSource="{Binding Path=Numbers}" Width="100" Height="500">
        <ListView.RenderTransform>
            <RotateTransform Angle="90" CenterX="150" CenterY="150"/>
        </ListView.RenderTransform> 
    </ListView>
</Grid>




public List<string> Numbers
{
    get
    {
        List<string> numbers = new List<string>();
        for (UInt16 i=1; i < 60000; i++)
        {
            numbers.Add(i.ToString());
        }
        return numbers;
    }
}
于 2012-11-09T21:49:46.053 に答える
0

さて、私はこれを少しの間バックバーナーに置いていましたが.

しかし、私は解決策を思いついたと思います。

DrawingVisual と DrawingContext を使用するには。

このブログ投稿はかなり役に立ちました: Simple WPF 2D Graphics: DrawingVisual

まず、Visual クラスを取得する必要があります (このコードはまとめられていることに注意してください。クリーンアップすることをお勧めします)。

public class MyVisualHost : FrameworkElement
{
private readonly VisualCollection children;

public MyVisualHost(int width)
{
    children = new VisualCollection(this);

    var visual = new DrawingVisual();
    children.Add(visual);

    var currentTime = new TimeSpan(0, 0, 0, 0, 0);
    const int everyXLine100 = 10;
    double currentX = 0;
    var currentLine = 0;
    double distanceBetweenLines = 5;
    TimeSpan timeStep = new TimeSpan(0, 0, 0, 0, 10);
    int majorEveryXLine = 100;

    var grayBrush = new SolidColorBrush(Color.FromRgb(192, 192, 192));
    grayBrush.Freeze();
    var grayPen = new Pen(grayBrush, 2);
    var whitePen = new Pen(Brushes.White, 2);
    grayPen.Freeze();
    whitePen.Freeze();

    using (var dc = visual.RenderOpen())
    {
        while (currentX < width)
        {
            if (((currentLine % majorEveryXLine) == 0) && currentLine != 0)
            {
                dc.DrawLine(whitePen, new Point(currentX, 30), new Point(currentX, 15));

                var text = new FormattedText(
                    currentTime.ToString(@"hh\:mm\:ss\.fff"),
                    CultureInfo.CurrentCulture,
                    FlowDirection.LeftToRight,
                    new Typeface("Tahoma"),
                    8,
                    grayBrush);

                dc.DrawText(text, new Point((currentX - 22), 0));
            }
            else if ((((currentLine % everyXLine100) == 0) && currentLine != 0)
                        && (currentLine % majorEveryXLine) != 0)
            {
                dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 20));

                var text = new FormattedText(
                    string.Format(".{0}", currentTime.Milliseconds),
                    CultureInfo.CurrentCulture,
                    FlowDirection.LeftToRight,
                    new Typeface("Tahoma"),
                    8,
                    grayBrush);
                dc.DrawText(text, new Point((currentX - 8), 8));
            }
            else
            {
                dc.DrawLine(grayPen, new Point(currentX, 30), new Point(currentX, 25));
            }

            currentX += distanceBetweenLines;
            currentLine++;
            currentTime += timeStep;
        }
    }
}

// Provide a required override for the VisualChildrenCount property.
protected override int VisualChildrenCount { get { return children.Count; } }

// Provide a required override for the GetVisualChild method.
protected override Visual GetVisualChild(int index)
{
    if (index < 0 || index >= children.Count)
    {
        throw new ArgumentOutOfRangeException();
    }

    return children[index];
}
}

次に、バインディングが必要です。

public static readonly DependencyProperty HeaderDrawingVisualProperty = DependencyProperty.Register("HeaderDrawingVisual", typeof(MyVisualHost), typeof(MainWindow));

public MyVisualHost VisualHost
{
    get { return (MyVisualHost)GetValue(HeaderDrawingVisualProperty); }
    set { SetValue(HeaderDrawingVisualProperty, value); }
}

XAML:

<Canvas x:Name="Header" Background="#FF2D2D30" Grid.Row="0">
    <ContentPresenter Content="{Binding HeaderDrawingVisual}" />
</Canvas>

次に、コードで設定します。

Header.Width = 50000;
VisualHost = new MyVisualHost(50000);

私のテストでは、この新しい方法で幅 50000 を使用したところ、大幅な増加が見られました。

Old way Total Milliseconds: 277.061
New way Total Milliseconds: 13.9982
Old way Total Milliseconds: 518.4632
New way Total Milliseconds: 12.9423
Old way Total Milliseconds: 479.1846
New way Total Milliseconds: 23.4987
Old way Total Milliseconds: 477.1366
New way Total Milliseconds: 12.6469
Old way Total Milliseconds: 481.3118
New way Total Milliseconds: 12.9678

再構築時にアイテムをクリアする必要があるため、最初の時間は短くなります。

于 2012-12-04T17:00:22.300 に答える