7

ビューを 1 秒間に最大 30 回更新する WPF のプロジェクトに取り組んでいます。私は自分の知る限り MVVM パターンを採用しており、これまでの結果にはかなり満足しています。ただし、ホスト コンテナーの VisualCollection 内で DrawingVisuals を更新するより効率的な方法がないかどうか疑問に思っています。ビューモデルのプロパティが変更されるたびに、そのビューモデルの新しい DrawingVisual を見つけて削除し、再度追加しています。常に移動するオブジェクトでは、DrawingVisuals 自体をビューモデルのプロパティに直接バインドするなど、より良い方法があるはずだと思いますが、それはどのように見えるでしょうか? シミュレーション内のモデルの数が増えるにつれて、更新のための合理化されたワークフローを確保する必要があります。ここの例に従って始めました: http://msdn.microsoft.com/en-us/library/ms742254。

非常に効率的な描画キャンバスが必要なため、DependencyProperties と UserControls をすべてのビューモデルにバインドすることを意図的に避けています (したがって、以下の QuickCanvas です)。そのため、メインの UI を設計し、ボタンとコマンドを接続する以外に、XAML はほとんど必要ありません。不明な点や重要な点を省略した場合は、質問してください。ありがとう!

視覚的なホスト コンテナー (ビュー):

public partial class QuickCanvas : FrameworkElement
{
    private readonly VisualCollection _visuals;
    private readonly Dictionary<Guid, DrawingVisual> _visualDictionary;

    public static readonly DependencyProperty ItemsSourceProperty =
        DependencyProperty.Register("ItemsSource", typeof(ObservableNotifiableCollection<IVisualModel>),
        typeof(QuickCanvas),
        new PropertyMetadata(OnItemsSourceChanged));

    public QuickCanvas()
    {
        InitializeComponent();
        _visuals = new VisualCollection(this);
        _visualDictionary = new Dictionary<Guid, DrawingVisual>();
    }

    public ObservableNotifiableCollection<IVisualModel> ItemsSource
    {
        set { SetValue(ItemsSourceProperty, value); }
        get { return (ObservableNotifiableCollection<IVisualModel>)GetValue(ItemsSourceProperty); }
    }

    protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
    {
        base.OnPropertyChanged(e);
        if (e.Property.Name == "Width" || e.Property.Name == "Height" || e.Property.Name == "Center")
        {
            UpdateVisualChildren();
        }
    }

    private void UpdateVisualChildren()
    {
        if (ItemsSource == null || _visuals.Count == 0) return;

        foreach (var model in ItemsSource)
        {
            var visual = FindVisualForModel(model);
            if (visual != null)
            {
                UpdateVisualFromModel(visual, model);
            }
        }
    }

    private void UpdateVisualPairFromModel(DrawingVisual visual, IVisualModel model)
    {
        visual.Transform = ApplyVisualTransform(visual.Transform, model);
    }

    private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args)
    {
        (obj as QuickCanvas).OnItemsSourceChanged(args);
    }

    private void OnItemsSourceChanged(DependencyPropertyChangedEventArgs args)
    {
        _visuals.Clear();

        if (args.OldValue != null)
        {
            var models = args.OldValue as ObservableNotifiableCollection<IVisualModel>;
            models.CollectionCleared -= OnCollectionCleared;
            models.CollectionChanged -= OnCollectionChanged;
            models.ItemPropertyChanged -= OnItemPropertyChanged;
        }

        if (args.NewValue != null)
        {
            var models = args.NewValue as ObservableNotifiableCollection<IVisualModel>;
            models.CollectionCleared += OnCollectionCleared;
            models.CollectionChanged += OnCollectionChanged;
            models.ItemPropertyChanged += OnItemPropertyChanged;

            CreateVisualChildren(models);
        }
    }

    private void OnCollectionCleared(object sender, EventArgs args)
    {
        _visuals.Clear();
        _visualDictionary.Clear();
    }

    private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        if (args.OldItems != null)
            RemoveVisualChildren(args.OldItems);

        if (args.NewItems != null)
            CreateVisualChildren(args.NewItems);
    }

    private void OnItemPropertyChanged(object sender, ItemPropertyChangedEventArgs args)
    {
        var model = args.Item as IVisualModel;
        if (model == null)
            throw new ArgumentException("args.Item was expected to be of type IVisualModel but was not.");
        //TODO is there a better way to update without having to add/remove visuals?
        var visual = FindVisualForModel(model);
        _visuals.Remove(visual);
        visual = CreateVisualFromModel(model);
        _visuals.Add(visual);
        _visualDictionary[model.Id] = visual;**
    }

    private DrawingVisual FindVisualForModel(IVisualModel model)
    {
        return _visualDictionary[model.Id];
    }

    private void CreateVisualChildren(IEnumerable models)
    {
        foreach (IVisualModel model in models)
        {
            var visual = CreateVisualFromModel(model);
            _visuals.Add(visual);
            _visuals.Add(visual);
            _visualDictionary.Add(model.Id, visual);
        }
    }

    private DrawingVisual CreateVisualFromModel(IVisualModel model)
    {
        var visual = model.GetVisual();
        UpdateVisualFromModel(visual, model);
        return visual;
    }

    private void RemoveVisualChildren(IEnumerable models)
    {
        foreach (IVisualModel model in models)
        {
            var visual = FindVisualForModel(model);
            if (visual != null)
            {
                _visuals.Remove(visual);
                _visualDictionary.Remove(model.Id);
            }
        }
    }

    protected override int VisualChildrenCount
    {
        get
        {
            return _visuals.Count;
        }
    }

    protected override Visual GetVisualChild(int index)
    {
        if (index < 0 || index >= _visuals.Count)
            throw new ArgumentOutOfRangeException("index");

        return _visuals[index];
    }
}

IVisuaModel impl:

public class VehicleViewModel : IVisualModel
{
    private readonly Vehicle _vehicle;
    private readonly IVisualFactory<VehicleViewmodel> _visualFactory;
    private readonly IMessageBus _messageBus;

    public VehicleViewmodel(Vehicle vehicle, IVisualFactory<VehicleViewmodel> visualFactory, IMessageBus messageBus)
    {
        _vehicle = vehicle;
        _visualFactory = visualFactory;
        _messageBus = messageBus;
        _messageBus.Subscribe<VehicleMovedMessage>(VehicleMoveHandler, Dispatcher.CurrentDispatcher);
        Id = Guid.NewGuid();
    }

    public void Dispose()
    {
        _messageBus.Unsubscribe<VehicleMovedMessage>(VehicleMoveHandler);
    }

    private void VehicleMoveHandler(VehicleMovedMessage message)
    {
        if (message.Vehicle.Equals(_vehicle))
            OnPropertyChanged("");
    }

    public Guid Id { get; private set; }
    public Point Anchor { get { return _vehicle.Position; } }
    public double Rotation { get { return _vehicle.Orientation; } }

    public DrawingVisual GetVisual()
    {
        return _visualFactory.Create(this);
    }

    public double Width { get { return _vehicle.VehicleType.Width; } }
    public double Length { get { return _vehicle.VehicleType.Length; } }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

IVisualFactory 実装:

public class VehicleVisualFactory : IVisualFactory<VehicleViewModel>
{
    private readonly IDictionary<string, Pen> _pens;
    private readonly IDictionary<string, Brush> _brushes;

    public VehicleVisualFactory(IDictionary<string, Pen> pens, IDictionary<string, Brush> brushes)
    {
        _pens = pens;
        _brushes = brushes;
    }

    public DrawingVisual Create(VehicleViewmodel viewModel)
    {
        var result = new DrawingVisual();
        using (var context = result.RenderOpen())
        {
            context.DrawRectangle(_brushes["VehicleGreen"], _pens["VehicleDarkGreen"],
                new Rect(-viewModel.Width / 2, -viewModel.Length / 2, viewModel.Width, viewModel.Length));
        }
        return result;
    }
}
4

2 に答える 2

1

つい最近、あなたが言及したMSDNの例に基づいて、DrawingVisualsのビジュアルコレクションを含むFrameworkElementであるVisualHost別名QuickCanvasの構造を使用し始めました。最初の描画時間は、UIElements を使用する場合よりも約 3 倍高速です。

次に、同じことに気付きました。DrawingVisuals の位置を変更するには、それらを削除してから再描画する必要があり、時間がかかります。観察では、除去にはほとんどの時間がかかるということさえありました!

部分的な解決策の場合-中間の答えは、より単純な問題(ジオメトリにオーバーラップが存在しない場合)の場合、単一の DrawingVisual を削除してから描画することができます。これにより、そのような場合の表示が 5 倍高速化されました。

しかし、VisualHosts を移動するような流暢な再描画が必要な場合 (DrawingVisuals をグループ関連の方法でグループ化/更新できる場合は、ケースで複数の QuickCanvas をレイアウトできます)、トリガーする方法が見つかりませんでしたOnRender、MeasureOverride などは、InvalidateVisual (Visual Host コンテナー上) を使用することも、Canvas または RenderTransform (コードから) ごとの配置に影響を与えることもありません。

私の必要性は、マウスごとに VisualHost のストレッチまたは移動を流暢に更新することです (たとえば、親指を使用)。
あなたも知っているように、OnPropertyChangedだけが発火するようです。

しかし、これまでのところ、うまくいきません。DrawingVisuals を使用した最初の描画は非常に高速ですが、インタラクティブ性は起動していません。すべてのハンドラーはローカルで増分イベントを発生させますが、単一の DrawingVisual または VisualHost コンテナーの位置にコードから直接影響を与える方法はありません。

                Canvas.SetLeft(element, left + xD);
                Canvas.SetTop(element, top + yD);

また

            transform.X += currentPoint.X - anchorPoint.X;
            transform.Y += currentPoint.Y - anchorPoint.Y;
            this.RenderTransform = transform;

(物事は計算しますが、レンダリングは行われません)。InvalidateVisual、Update、またはその他のメカニズムのいずれも、VisualHost コンテナー (別名 QuickCanvas) を起動して描画するようには見えません。

于 2014-03-17T13:11:38.800 に答える