How can i put an UIElement on the SvgDrawingCanvas?

Topics: Converters and Runtime, Tips and How-To
Aug 10, 2011 at 9:50 AM
Edited Aug 10, 2011 at 9:53 AM

 I'm exploring the SvgDrawingCanvas in namespace  SharpVectors.Runtime, which is employed in WpfTestSvgSample.DrawingPage.

However, i want to put my UIElement on the SvgDrawingCanvas, since it inherits directly from System.Windows.Controls.Canvas. So i just want to call the canvas.Children.Add(uiElement) method, but it just doesn't work.

The code is to be like this:

		  Ellipse myEllipse = new Ellipse();
                    myEllipse.Stroke = System.Windows.Media.Brushes.Black;
                    myEllipse.Fill = System.Windows.Media.Brushes.DarkBlue;
                    myEllipse.HorizontalAlignment = HorizontalAlignment.Left;
                    myEllipse.VerticalAlignment = VerticalAlignment.Center;
                    myEllipse.Width = 50;
                    myEllipse.Height = 75;                    
                    svgDrawingPage.SvgViewer.Children.Add(myEllipse);
where the svgDrawingPage is an instance of WpfTestSvgSample.DrawingPage. 

then i have to write like that:

                    drawingGroup.Children.Add(ellipseDrawing);
		  svgDrawingPage.SvgViewer.RenderDiagrams(drawingGroup);
where the ellipseDrawing is an instance of System.Windows.Media.Drawing, and it does not interactive with our users.
here is my email: liuweizzuie@qq.com, if anyone has a solution, please reply, or write to me.
Expecting, and thank you very much.
Coordinator
Aug 10, 2011 at 10:13 AM

Currently, it may not work that way. If you take a look at the source of SvgDrawingCanvas, we will find this

 protected override int VisualChildrenCount
        {
            get 
            {
                if (_hostVisual != null)
                {
                    return 1;
                }

                return 0;
            }
        }

So, the total number of visuals returned is either 1 or 0. You can derived from this class and override this property
to tell the framework the number of children you are drawing and also the related method...

// Provide a required override for the GetVisualChild method.
        protected override Visual GetVisualChild(int index)
        {
            if (_hostVisual == null)
            {
                return null;
            }

            return _hostVisual;
        }

NOTE: I am writing from work, about to go home but I do not use computer at home on Wednesday (Japan time)
and will not be able to reply till tomorrow. However, you can still post any further question you have I will reply
when I come back online. 

Best regards,
Paul. 

Aug 11, 2011 at 2:31 AM

Hello, Paul.

      Thanks for replying, but sorry to trouble you again. For I  rewrited the Property VisualChildrenCount and the method GetVisualChild, but it didn't work as expected.

      We assume that  _hostVisual is the last child of  SvgDrawingCanvas, so the total Count should be (this.Children.Count + 1), and the code looks like:

             //Provide a required override for the VisualChildrenCount property.
        protected override int VisualChildrenCount
        {
            get 
            {
                if (_hostVisual != null)
                {
                    return this.Children.Count + 1;
                }

                return this.Children.Count;
            }
        }
  
   Then the method:
        //Provide a required override for the GetVisualChild method.
        protected override Visual GetVisualChild(int index)
        {
            if (index < this.Children.Count)
            {
                return this.Children[index];
            }
            else
            {
                return _hostVisual;
            }
        }
And I added a button to svgViewer,
        
        <svg:SvgDrawingCanvas x:Name="svgViewer" >
                    <Button Content="Click me!" Width="75" Height="29"   />
        </svg:SvgDrawingCanvas>
Then I run the  project WpfTestSvgSample, and the button came out when the app showed up. When it came to the svg graphics, I got confused because the button disapeared. 
So I guess the button was under the drawing issue. MSDN said that method ArrangeOverride should be overrided, too. But I have no idea of that. 
Aug 11, 2011 at 2:38 AM

Sorry for changing your code.

It's just for a test, It's diffcult to derive from the class, because _hostVisual is private, and it can't be seen from a child class.

It's Thursday, 10:38 am in China now, and wish you a good day.

Coordinator
Aug 11, 2011 at 4:15 AM

Hello,

The easy approach is to create a member field like 
private VisualCollection _children;

and control the whole process. You add the _hostVisual also to this collection.
You have to override the Visual.AddVisualChild Method too and add to the collection.

Currently, it seems the system is getting different result at different times. The zero index
should be the _hostVisual object, if you want your objects on top. 

Another way is to maintain the current canvas class and use it as a child, and then add
another layer on top of that. This is the approach, I thought will be most common.

Fortunately, today I am on holiday so let me know the approach you wish to
use, I could provide you with a working code. 

BTW, there is nothing wrong changing the code, it is open for any use! It is better, however, if you
rename it and maintain your own copy in the local project so that updates will not affect you.

Also, provide your first name so that I could address you by your name :)

Best regards,
Paul. 

Aug 11, 2011 at 6:26 AM
Edited Aug 11, 2011 at 6:39 AM

Hello,

You are quite right, and the _hostVisual should be the Children[0], so that it will under other elements.

But I found that the button will interactive with the mouse  events, and that's all I want.
So I decide not to go further, because the deadline is comming.

I'm very glad for your help, my full name is Liu Wei, Liu is my family name.
And ZZU stands for Zhengzhou University, IE is "Information Engineering School".  
I'm a bachelor of  the department of Computer Science and Techonology.
So usually  I login with the name "liuweizzuie".

And, I added a method to the WpfTestSvgSample.DrawingPage, which can load a SVG file from stream,
for the data is stored in a DataBase.

Here is the code:

public static readonly DependencyProperty MapStreamProperty =
            DependencyProperty.RegisterAttached(
                "MapStream",
                typeof(Stream),
                typeof(SVGDrawingControl),
                new FrameworkPropertyMetadata(null, new PropertyChangedCallback(MapStreamChanged)));

        [Browsable(true), Description("地图文件")] //"地图文件" is Chinese Chacator, means "Map File".
        public Stream MapStream
        {
            get 
            { 
                return (Stream)GetValue(MapStreamProperty); 
            }
            set
            {
                SetValue(MapStreamProperty, value);
            }
        }

        static void MapStreamChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
        {
            SVGDrawingControl svgDrawingControl = (SVGDrawingControl)depObj;

            //svgDrawingControl.Dispatcher.Invoke(new Action(() =>
            //{
            //    svgDrawingControl.svgViewer.Children.Clear();
            //}));

            svgDrawingControl.LoadFromStream((Stream)e.NewValue);
        }

        public bool LoadFromStream(Stream stream)
        {
            svgViewer.UnloadDiagrams();
            if (stream == null || stream.Length == 0) return false;

            _fileReader.SaveXaml = _saveXaml;
            _fileReader.SaveZaml = false;
            
            DrawingGroup drawing = _fileReader.Read(stream);

            if (drawing != null)
            {
                BackgroundWorker bkWorker_RenderDiagrams = new BackgroundWorker();
                bkWorker_RenderDiagrams.DoWork += new DoWorkEventHandler(bkWorker_RenderDiagrams_DoWork);
                bkWorker_RenderDiagrams.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bkWorker_RenderDiagrams_RunWorkerCompleted);
                bkWorker_RenderDiagrams.RunWorkerAsync(drawing);
                return true;
            }

            return false;
        }

        private void bkWorker_RenderDiagrams_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {

            Rect bounds = svgViewer.Bounds;
            
            if (bounds.IsEmpty)
            {
                bounds = new Rect(0, 0,
                    canvasScroller.ActualWidth, canvasScroller.ActualHeight);
            }
            zoomPanControl.AnimatedZoomTo(bounds);
        }

        private void bkWorker_RenderDiagrams_DoWork(object sender, DoWorkEventArgs e)
        {
            DrawingGroup drawing = e.Argument as DrawingGroup;

            this.Dispatcher.Invoke(new Action(() => {
                svgViewer.RenderDiagrams(drawing);
            }));

            NotifyMapLoaded(drawing);
        }
NofifyMapLoaded raises the event, to notify a manager to draw something onto the canvas. 
May you happy everyday!
Yours, 
Liu Wei

 

 

Coordinator
Aug 11, 2011 at 6:52 AM

Hello Wei,

Thanks for the information, happy to know you got it working.

If you run into any other problem, please let me know. Best wishes in your work.

Best regards,
Paul. 

Aug 11, 2011 at 10:04 AM
Edited Aug 11, 2011 at 10:09 AM

Hello Paul,

I debuged my code for hours, but just got frusrated. As you said, it seems the system is getting different result at different times.

I wrote a private field:
    private VisualCollection _children;
And the Property VisualChildrenCount and the method GetVisualChild is set to be wrapping of _children. 
  
Then, AddVisualChild should be overrided to operate _children, but this method is decleared as:
    protected void AddVisualChild (Visual child);

And the only related method that can be overrided is OnVisualChildrenChanged, when I go more deep, it seems that
the base class Virtual has a private field the same as _children, too. 
Hence a VirtualChild will be referenced in two Collections, is it so?

Yours,
Wei.

 

   

Coordinator
Aug 11, 2011 at 10:56 AM
Edited Aug 11, 2011 at 11:03 AM

Hello Wei,

I am sorry, I did not check on that method well. This is the second approach, which is simple and you will
not have to change the SvgDrawingCanvas codes.

Change the XAML code in the DrawingPage.xaml to look like this

<Canvas Name="LayerContainer" Width="{Binding ElementName=svgViewer, Path=ActualWidth}"
         Height="{Binding ElementName=svgViewer, Path=ActualHeight}">
    <!-- This Canvas is the content that is displayed by the ZoomPanControl.
    Width and Height determine the size of the content. -->
    <svg:SvgDrawingCanvas x:Name="svgViewer" Background="White"/>

    <Canvas Name="UserLayer" Width="{Binding ElementName=svgViewer, Path=ActualWidth}"
             Height="{Binding ElementName=svgViewer, Path=ActualHeight}">                    
        <Button Background="Red" Content="TestButton" Width="75" Height="29"/>
    </Canvas>                    
</Canvas>

 That is replace the following with the above

<svg:SvgDrawingCanvas x:Name="svgViewer" Background="White"/>
This approach creates two layers, the svgViewer layer for SVG drawing and the UserLayer layer
for your drawings or controls. By using the binding, we keep the dimension the same and based
on the SVG drawing layer. The LayerContainer now hosts the two layers as children. Like that you can have any number of layers.
Please try this and let me know if it works for you. Again, sorry for the previous mistake.
Best regards,
Paul. 
Aug 15, 2011 at 11:11 PM
Edited Aug 15, 2011 at 11:17 PM

Hello Paul,

Thank you very much for your xaml code. It works very well, and, I'm pushing forword to make some controls.

I drew something on my controls according to the db, and put them on the right positions. It took me several days,
and I  dropped in my art, nearly having no time to lookback.

It's the 2nd year of my career, before I've never communacated with such a  talented coder like you,
because I'm the only coder in my company.
It's an exciting work, though my boss usually doesn't known what am I doing :) 
I'm responsable for my progam, just like you are.
And I didn't change your dlls, when the program completes, I'll let you know.

So I find codeplex is  a nice place, and you are a nice man.

May you happy everyday, and everything goes well !

Yours,
Wei