How to draw graphics as efficiently as possible in WPF

后端 未结 3 686
情歌与酒
情歌与酒 2021-01-31 05:05

I am creating a tool which relies heavily on graph-node trees. The current implementation is done in Java and I\'m porting it to a generic code-base on C#, so it can be used by

3条回答
  •  说谎
    说谎 (楼主)
    2021-01-31 05:39

    Generally, better performance is obtained with lower-level services. In WPF, this means the Drawing family of objects. All you get are: Drawing, DrawingGroup, GeometryDrawing, GlyphRunDrawing, ImageDrawing, and VideoDrawing. However, they are sufficient for all needs. Using these types is very friendly with WPF because Drawing is the conceptual unit that WPF exchanges with your GPU accelerator, possibly retaining and managing it there if possible. This works because the Drawing is expressed in terms of portable vector drawing primitives.

    Once you start re-architecting your app around Drawings however, you might need some interop with your higher-level code which is still based on UIElement, FrameworkElement, etc. One thing that I haven't found built-in to WPF is a simple way to wrap a Drawing as a FrameworkElement in the lowest-overhead way possible. DrawingVisual isn't a complete solution, because it only derives from Visual--meaning it still requires a hosting element.

    The following class will host any WPF Drawing directly without using an intermediate DrawingVisual. I added support for FrameworkElement's Margin property (with no performance penalty if unused) but little else. Because of WPF's single rendering thread it's safe and easy to cache a single TranslateTransform object to implement the margin. I'd recommend that you supply only drawings which have been Frozen; in fact, in the version that I use, I have an assert to that effect in the constructor.

    public class DrawingElement : FrameworkElement
    {
        static readonly TranslateTransform tt_cache = new TranslateTransform();
    
        public DrawingElement(Drawing drawing)
        {
            this.drawing = drawing;
        }
        readonly Drawing drawing;
    
        TranslateTransform get_transform()
        {
            if (Margin.Left == 0 && Margin.Top == 0)
                return null;
            tt_cache.X = Margin.Left;
            tt_cache.Y = Margin.Top;
            return tt_cache;
        }
        protected override Size MeasureOverride(Size _)
        {
            var sz = drawing.Bounds.Size;
            return new Size
            {
                Width = sz.Width + Margin.Left + Margin.Right,
                Height = sz.Height + Margin.Top + Margin.Bottom,
            };
        }
        protected override void OnRender(DrawingContext dc)
        {
            var tt = get_transform();
            if (tt != null)
                dc.PushTransform(tt);
            dc.DrawDrawing(drawing);
            if (tt != null)
                dc.Pop();
        }
    };
    

    [edit:] This is also useful for inserting a WPF Drawing into the InlineUIContainer.Child property (i.e. using TextBlock.InlinesCollection to format the contents of the TextBlock more richly).

提交回复
热议问题