ElinamLLC / SharpVectors

SharpVectors - SVG# Reloaded: SVG DOM and Rendering in C# for the .Net.
https://elinamllc.github.io/SharpVectors/
BSD 3-Clause "New" or "Revised" License
697 stars 134 forks source link

Make SvgDrawingCanvas Drawings public #88

Closed djonasdev closed 3 years ago

djonasdev commented 5 years ago

Actually I want to change the color of the svg via XAML or code behind. Due to the private modifier, it is not possible to access the objects (see: https://github.com/ElinamLLC/SharpVectors/blob/master/Source/SharpVectorRuntimeWpf/SvgDrawingCanvas.cs#L49).

I found a possibility to get it working via reflection:

SvgDrawingCanvas canvas = (SvgDrawingCanvas) foo.Child;
List<Drawing> drawings = (List<Drawing>)typeof(SvgDrawingCanvas).GetField("_drawObjects", BindingFlags.NonPublic | BindingFlags.Instance).GetValue(canvas);

foreach (Drawing drawing in drawings)
{
    if (drawing is GeometryDrawing geometryDrawing)
    {
        geometryDrawing.Brush = Brushes.BlueViolet;
    }
}

Is there a special reason why they are private?

paulushub commented 5 years ago

SvgDrawingCanvas is based on an initial work for a customer, before the library was made public. That is why most parts are commented out. The Runtime classes and the controls like SvgViewbox needs a lot more improvements to be more useful and I have these on my TODO, just trying to improve the conversion process first for now.

Now, what is the actual requirements of your project? Do you need the SvgDrawingCanvas? SvgDrawingCanvas is only useful if you separate the processes; conversion and viewing. The initial requirement for a car wiring manual, converts all the SVG files to XAML files and combine with documentations/instructions in FlowDocument XAML to create an interactive WPF-based manual. So the SvgDrawingCanvas was for the viewing application at the end-users, the conversion library was not shipped to the end-users; only the Runtime library. For most other projects, the controls might be more useful so if you do not mind can you state the actual requirements?

djonasdev commented 5 years ago

I think it's great that you bring the library back to life and continue to develop! 😀👍

In my project I have a plant visualization! From SolidWorks I can export individual modules as SVG. This makes it possible to implement the layout 1: 1 in my C # application without much effort.

Since it is also possible to select individual stations, I have to color my modules. Therefore, I need access to the individual elements of the SVG.


In detail I have a Grid and two SvgViewbox. The background svg is the part of the module which I color.

<Grid>
    <svgc:SvgViewbox x:Name="backgroundSvg" Source="4H040339_bg.svg"/>
    <svgc:SvgViewbox x:Name="svg"  Source="4H040339.svg"/>
</Grid>

2019-02-12 13_33_22-window

svg


As an additional improovement would be to get acces to a named layer inside of the svg. So I could have 2 layer inside of my svg and only color the "background_layer". This could save me a SvgViewbox.

EDIT

You wrote: SvgDrawingCanvas is only useful if you separate the processes

It would be amazing if it is possible to let the Threadpool make a conversion between svg and xaml. So the gui would be much more responsive! Actually I have a virtualization with more than 60 Objects. If I need to use two SvgViewbox, I have 120 conversion which run on the gui thread. 🤔

paulushub commented 5 years ago

Thanks for the compliments, happy to know this library is useful to your works.

Therefore, I need access to the individual elements of the SVG.

Will modify the controls to allow access to the loaded SVG document, will drop the use of converter classes in the controls. I have being looking for a simple way to let developers use the Element visitor to make modifications in the SVG document at runtime, maybe through a dependency injection.

So I could have 2 layer inside of my svg and only color the "background_layer".

You can loop through the drawings in the DrawingGroup to find the background_layer. Layers are converted to DrawingGroup in the drawings.

It would be amazing if it is possible to let the Threadpool make a conversion between svg and xaml.

The library does not impose any application framework, the SvgDrawingCanvas does not handle conversions, these are handled by the SvgConverter derived classes. The converter classes can be ran in a different thread and even in different .NET domain.

The controls such as SvgViewbox and SvgCanvas do handle conversions, so we could support background conversion here including support for async/await procedures in .NET45 versions.

Just for information, do you have animations in the SVG files?

djonasdev commented 5 years ago

Will modify the controls to allow access to the loaded SVG document, will drop the use of converter classes in the controls. I have being looking for a simple way to let developers use the Element visitor to make modifications in the SVG document at runtime, maybe through a dependency injection.

I do not understand exactly what you want to tell me

You can loop through the drawings in the DrawingGroup to find the background_layer. Layers are converted to DrawingGroup in the drawings. Ah ok! I have found another way to solve my problem with only 1 svg and without any layers. The GeometryDrawing.Brush property is the background of the path in the svg. So in default I have a transparant background and the contour is black. So if I modify the GeometryDrawing.Brush property, the background gets changed.

I wonder just if it is also possible to specify the contour, since currently only the background (of the svg) is bound to the GeometryDrawing.Brush property.

The controls such as SvgViewbox and SvgCanvas do handle conversions, so we could support background conversion here including support for async/await procedures in .NET45 versions.

That's a good solution! Then the gui would be much more response! Actually I have more than 4seconds until the Page has been loaded..

Just for information, do you have animations in the SVG files?

It is very simple. I made a Binding in code behind between a public DependencyProperty of the customControl and BrushProperty. Then in my XAML Style definition, I have a Storyboard to animate the DependencyProperty

foreach (Drawing drawing in drawings)
{
    if (drawing is GeometryDrawing geometryDrawing)
    {
        BindingOperations.SetBinding(geometryDrawing, GeometryDrawing.BrushProperty, fillBinding);
    }
}
paulushub commented 5 years ago

I do not understand exactly what you want to tell me

I think we need more documentations of the library features since mode is being added. Basically, you can modify the SVG DOM if the Svg Document object is exposed. It is an instance of XmlDocument, so you can query, modify etc. The statement is how to make it useful for developers using the controls in their applications.

Ah ok! I have found another way to solve my problem with only 1 svg and without any layers.

Personally, I think the layer could make the work clearer. Design tools allow layer creation so the flow will be natural.

It is very simple. I made a Binding in code behind between a public DependencyProperty of the...

You only need more access to the created Drawing objects, will commit those changes soon.

paulushub commented 5 years ago

@dojo90 Please see if this commit addresses your needs 6eec55531b23d76a9fc3ebf1b3b1511e0cbe2ee4

djonasdev commented 5 years ago

I think we need more documentations of the library features since mode is being added. Basically, you can modify the SVG DOM if the Svg Document object is exposed. It is an instance of XmlDocument, so you can query, modify etc.

But that means you have to 'redraw'/'render' the svg after modifing the source. For my personal needs, it's enough to get access to each object of the rendered svg. Maybe other people want to modify the svg via their app. With your last commit https://github.com/ElinamLLC/SharpVectors/commit/6eec55531b23d76a9fc3ebf1b3b1511e0cbe2ee4 a developer has access to all the rendered objects. That is exactly what I was asking for 😎 👍

I just wonder if it is also possible to specify the contour, since currently only the background (of the svg) is bound to the GeometryDrawing.Brush property.

Could you please take a look at this quote

Lak4CYUT commented 4 years ago

It is very simple. I made a Binding in code behind between a public DependencyProperty of the customControl and BrushProperty. Then in my XAML Style definition, I have a Storyboard to animate the DependencyProperty

foreach (Drawing drawing in drawings)
{
  if (drawing is GeometryDrawing geometryDrawing)
  {
      BindingOperations.SetBinding(geometryDrawing, GeometryDrawing.BrushProperty, fillBinding);
  }
}

@dojo90 Hi, I am looking for a way to change svg foreground color and I just saw your solution in here. Thanks for your idea, it is help me a lot, but I don't really understand how to bind a non-DependencyProperty to my ViewModel (Child? Drawings?). I can access SvgViewbox via x:Name but I will more like to access it with ViewModel (MVVM), could you please give me more hint? Thanks.

Lak4CYUT commented 4 years ago

After a couple research, I found my solution. It is reference to @dojo90 's sample code, but it don't need to add code on code-behind. Just create a custom dependency property and bind it.

    public class MySvgViewbox : SvgViewbox
    {
        public static readonly DependencyProperty BrushProperty = 
            DependencyProperty.Register("Brush", typeof(Brush), 
                typeof(MySvgViewbox), new PropertyMetadata(
                    Brushes.BlueViolet));

        public Brush Brush
        {
            get { return (Brush)GetValue(BrushProperty); }
            set { SetValue(BrushProperty, value); }
        }

        protected override void OnSettingsChanged()
        {
            base.OnSettingsChanged();
            IList<Drawing> drawings = ((SvgDrawingCanvas)Child).DrawObjects;
            foreach (Drawing drawing in drawings)
            {
                if (drawing is GeometryDrawing geometryDrawing)
                {
                    Binding b = new Binding("Brush")
                    {
                        Source = this,
                        Mode = BindingMode.OneWay
                    };
                    BindingOperations.SetBinding(geometryDrawing, GeometryDrawing.BrushProperty, b);
                }
            }
        }
    }
stojy commented 3 years ago

Sorry to reserruct this issue, but is there a plan to include this feature into SvgViewbox?

If so, you might find the folllowing implementation useful as it also allows for overriding the svg's stroke color.

    public class SvgViewboxEx : SvgViewbox
    {
        public static readonly DependencyProperty FillProperty = DependencyProperty.Register("Fill", typeof(Brush), typeof(SvgViewboxEx), new PropertyMetadata(Brushes.BlueViolet));

        public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register("Stroke", typeof(Brush), typeof(SvgViewboxEx), new PropertyMetadata(Brushes.DarkViolet));

        public Brush Fill
        {
            get => (Brush) GetValue(FillProperty);
            set => SetValue(FillProperty, value);
        }

        public Brush Stroke
        {
            get => (Brush) GetValue(StrokeProperty);
            set => SetValue(StrokeProperty, value);
        }

        protected override void OnSettingsChanged()
        {
            base.OnSettingsChanged();

            var drawings = ((SvgDrawingCanvas) Child).DrawObjects;

            foreach (var drawing in drawings)
            {
                if (drawing is GeometryDrawing geometryDrawing)
                {
                    // svg fill color - translated to a geometry.Brush
                    var brush = new Binding(nameof(Fill))
                                    {
                                        Source = this,
                                        Mode = BindingMode.OneWay
                                    };
                    BindingOperations.SetBinding(geometryDrawing, GeometryDrawing.BrushProperty, brush);

                    // svg stroke color - translated to a geometry.Pen.Brush
                    if (geometryDrawing.Pen != null)
                    {
                        var stroke = new Binding(nameof(Stroke))
                                         {
                                             Source = this,
                                             Mode = BindingMode.OneWay
                                         };
                        BindingOperations.SetBinding(geometryDrawing.Pen, Pen.BrushProperty, stroke);
                    }
                }
            }
        }
    }
paulushub commented 3 years ago

@stojy Do you mean the binding operations suggested by @Lak4CYUT? The original thread was on making the drawing in SvgDrawingCanvas public, which really is not an issue since SvgDrawingCanvas does not perform conversion by itself. So, @Lak4CYUT post should be in a different thread to be addressed properly.

You can post a new issue with your suggestions and reference this one. This is similar to the discussion here Change svg image colors at runtime?.

stojy commented 3 years ago

@paulushub, that's correct.. I was referring to @Lak4CYUT's binding suggestion. I'll make a fresh posting as suggested.