none
WPF custom drawing markers

    Question

  • I was provided with the code below to enable me to draw markers on a WPF window via a canvas (many thanks Thorsten). See the marker below. The solution works great but the marker size is relative to the size of the canvas. How can I adapt this solution so as the marker size remains constant irrespective of the size of the canvas. Please bear in mind that the canvas will vary in size and I need to be able to draw markers of the same size on any part of the canvas. So if the canvas is 300 x 300 or 1000 x 1000 markers should stay the same size irrespective. It appears to me the scaling is done when rendering to a bitmap in the last few lines of code. However I am no expert in custom drawing with WPF.


    Many thanks

     private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                Point p = e.GetPosition(canvas1);
    
                DrawingVisual dv = new DrawingVisual();
    
                PathGeometry pG = new PathGeometry();
    
                double ang = 55.0 / 180.0 * Math.PI;
                Point p1 = new Point(0, -35);
                Point pL = new Point(Math.Sin(ang) * 35.0, 35 - (Math.Cos(ang) * 35.0));
                Point pR = new Point(-pL.X, pL.Y);
                PathFigure pFig = new PathFigure();
                pFig.StartPoint = pL;
                ArcSegment arc = new ArcSegment(pR, new Size(35, 35), 0, true, SweepDirection.Clockwise, false);
                pFig.Segments.Add(arc);
                pFig.Segments.Add(new LineSegment(p1, false));
                pFig.IsClosed = true;
                pG.Figures.Add(pFig);
    
                using (DrawingContext dc = dv.RenderOpen())
                {
                    dc.PushTransform(new TranslateTransform(p.X, p.Y));
                    dc.PushTransform(new RotateTransform(-90));
                    dc.DrawGeometry(Brushes.Cyan, null, pG);
                    dc.Pop();
                    FormattedText ft = new FormattedText("1", System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                        new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal), 36, Brushes.White);
                    dc.DrawText(ft, new Point(20, -20));
                    dc.Pop();
                }
    
                dv.Effect = new System.Windows.Media.Effects.DropShadowEffect();
    
                //render to a bitmap and assign to the Control
                RenderTargetBitmap rtb = new RenderTargetBitmap((int)this.canvas1.Width, (int)this.canvas1.Height, 96, 96, PixelFormats.Pbgra32);
                rtb.Render(dv);
                this.image1.Source = rtb;
            }










    Sunday, May 21, 2017 8:51 PM

Answers

  • Hi,

    >> The solution works great but the marker size is relative to the size of the canvas.  How can I adapt this solution so as the marker size remains constant irrespective of the size of the canvas.

    Try following code.

     public partial class DrawingMarker : Window
        {
            public DrawingMarker()
            {
                InitializeComponent();
            }
    
            private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                //get mouse position 
                Point p = e.GetPosition(canvas1);
                //add marker in canvas
                Image img = new Image();
                img.Source = createMarker("marker");
                img.Stretch = Stretch.None;
                Canvas.SetLeft(img, p.X);
                Canvas.SetTop(img, p.Y-35);
                canvas1.Children.Add(img);
            }
            private RenderTargetBitmap createMarker(string text)
            {
                DrawingVisual dv = new DrawingVisual();
                PathGeometry pG = new PathGeometry();
                double ang = 55.0 / 180.0 * Math.PI;
                Point p1 = new Point(0, -35);
                Point pL = new Point(Math.Sin(ang) * 35.0, 35 - (Math.Cos(ang) * 35.0));
                Point pR = new Point(-pL.X, pL.Y);
                PathFigure pFig = new PathFigure();
                pFig.StartPoint = pL;
                ArcSegment arc = new ArcSegment(pR, new Size(35, 35), 0, true, SweepDirection.Clockwise, false);
                pFig.Segments.Add(arc);
                pFig.Segments.Add(new LineSegment(p1, false));
                pFig.IsClosed = true;
                pG.Figures.Add(pFig);
    
                using (DrawingContext dc = dv.RenderOpen())
                {
                    dc.PushTransform(new TranslateTransform(35, 35));
                    dc.PushTransform(new RotateTransform(-90));
                    dc.DrawGeometry(Brushes.Red, null, pG);
                    dc.Pop();
                    FormattedText ft = new FormattedText(text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                        new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal), 16, Brushes.White);
                    dc.DrawText(ft, new Point(10, -10));
                    dc.Pop();
                }
    
                dv.Effect = new System.Windows.Media.Effects.DropShadowEffect();
    
                //render to a bitmap and assign to the Control
                RenderTargetBitmap rtb = new RenderTargetBitmap(120, 120, 96, 96, PixelFormats.Pbgra32);
                rtb.Render(dv);
                return rtb;
            }
        }
    <Grid>
            <Canvas Height="300" Width="300" Background="AliceBlue" x:Name="canvas1" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" />
        </Grid>


    Best Regards,

    Bob


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.


    Monday, May 22, 2017 6:29 AM
    Moderator

All replies

  • Hi,

    >> The solution works great but the marker size is relative to the size of the canvas.  How can I adapt this solution so as the marker size remains constant irrespective of the size of the canvas.

    Try following code.

     public partial class DrawingMarker : Window
        {
            public DrawingMarker()
            {
                InitializeComponent();
            }
    
            private void Canvas_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
            {
                //get mouse position 
                Point p = e.GetPosition(canvas1);
                //add marker in canvas
                Image img = new Image();
                img.Source = createMarker("marker");
                img.Stretch = Stretch.None;
                Canvas.SetLeft(img, p.X);
                Canvas.SetTop(img, p.Y-35);
                canvas1.Children.Add(img);
            }
            private RenderTargetBitmap createMarker(string text)
            {
                DrawingVisual dv = new DrawingVisual();
                PathGeometry pG = new PathGeometry();
                double ang = 55.0 / 180.0 * Math.PI;
                Point p1 = new Point(0, -35);
                Point pL = new Point(Math.Sin(ang) * 35.0, 35 - (Math.Cos(ang) * 35.0));
                Point pR = new Point(-pL.X, pL.Y);
                PathFigure pFig = new PathFigure();
                pFig.StartPoint = pL;
                ArcSegment arc = new ArcSegment(pR, new Size(35, 35), 0, true, SweepDirection.Clockwise, false);
                pFig.Segments.Add(arc);
                pFig.Segments.Add(new LineSegment(p1, false));
                pFig.IsClosed = true;
                pG.Figures.Add(pFig);
    
                using (DrawingContext dc = dv.RenderOpen())
                {
                    dc.PushTransform(new TranslateTransform(35, 35));
                    dc.PushTransform(new RotateTransform(-90));
                    dc.DrawGeometry(Brushes.Red, null, pG);
                    dc.Pop();
                    FormattedText ft = new FormattedText(text, System.Globalization.CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
                        new Typeface(new FontFamily("Arial"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal), 16, Brushes.White);
                    dc.DrawText(ft, new Point(10, -10));
                    dc.Pop();
                }
    
                dv.Effect = new System.Windows.Media.Effects.DropShadowEffect();
    
                //render to a bitmap and assign to the Control
                RenderTargetBitmap rtb = new RenderTargetBitmap(120, 120, 96, 96, PixelFormats.Pbgra32);
                rtb.Render(dv);
                return rtb;
            }
        }
    <Grid>
            <Canvas Height="300" Width="300" Background="AliceBlue" x:Name="canvas1" MouseLeftButtonUp="Canvas_MouseLeftButtonUp" />
        </Grid>


    Best Regards,

    Bob


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.


    Monday, May 22, 2017 6:29 AM
    Moderator
  • Wonderful Bob thank you so much, very much appreciated. Works beautifully.
    Monday, May 22, 2017 5:04 PM
  • Using the code above from Bob how can I adjust the background capacity of the marker but not the text opacity which should remain at 1.0?

    Thanks
    Friday, May 24, 2019 11:53 PM
  • In the line:

    dc.DrawGeometry(Brushes.Red, null, pG);

    Instead of using a standard Brush ( Brushes.Red ) you can create your own custom brush.

    That should be a solidcolorbrush which has an opacity property

                SolidColorBrush scb = Brushes.Red;
                scb.Opacity = .3;


    You can alternatively define a color using hex with argb. A being opacity.

    eg 

    SolidColorBrush scb = (SolidColorBrush)(new BrushConverter().ConvertFrom("#4CFA0404"));

    I've shown you this specifically because that brushconverter is something which is automatically used when you define a brush using one of those hex codes as colour.

    You could alternatively define a brush in a resource dictionary, merge that in app.xaml and then go get it back out of application.current.resources to use in code.

    Personally.

    I would prefer all that stuff to be a template defined in xaml rather than code.

    Unless you have a HUGE amount of those markers, use of rendertargetbitmap is overkill.

    On a computer regarded pretty old and creaky by standards of today you can have thousands of quite complicated templates displayed on a canvas without performance issues.

    Our map editor has usercontrols for various things, one of which is trees.

    I template out thousands - maybe ~10,000 - on maps which have woods covering most of them.


    Hope that helps.

    Technet articles: WPF: Layout Lab; All my Technet Articles

    Saturday, May 25, 2019 2:21 PM
    Moderator