tags:

views:

1077

answers:

2

I'm creating a Path in Silverlight, and adding elements to it on mouse events. But, although the elements are there in memory, the screen doesn't get updated until something else causes a screen repaint to happen.

Here's the relevant code - I'm responding to a mouse event, and I keep a class member of the path I'm editing.

 Path path = null;

 private void LayoutRoot_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
 {
  Point thisPoint = e.GetPosition(LayoutRoot);
  if (path == null)
  {
   CreateNewPath(thisPoint);
   path.LayoutUpdated += new EventHandler(path_LayoutUpdated);
  }
  else
  {
   path.AddLineElement(thisPoint);
  }
 }

 private void CreateNewPath(Point startPoint)
 {
  path = new Path();
  PathGeometry geometry = new PathGeometry();
  path.Data = geometry;
  PathFigureCollection figures = new PathFigureCollection();
  geometry.Figures = figures;
  PathFigure figure = new PathFigure();
  figures.Add(figure);
  figure.StartPoint = startPoint;
  figure.Segments = new PathSegmentCollection();
  path.Stroke = new SolidColorBrush(Colors.Red);
  path.StrokeThickness = 2;
  path.Stretch = Stretch.None;
  LayoutRoot.Children.Add(path);
 }

AddLineElement is an extension method for the path class just to simplify:

public static class PathHelper
{
 public static void AddLineElement(this Path thePath, Point newPoint)
 {
  PathGeometry geometry = thePath.Data as PathGeometry;
  geometry.Figures[0].Segments.Add(new LineSegment { Point = newPoint });
 }
}

This is the minimum needed to reproduce the problem. If you run this code in a full WPF app it all works as expected. Mouse clicks add line elements which appear immediately. However, in Silverlight it's a different matter. The clicks appear to do nothing, even though stepping through the code shows that the data is getting added. But if you click a few times, then resize the browser, for example, the path elements appear. If you happen to have a button on the page as well, and move the mouse over, the path will appear.

I've tried all the obvious things, like calling InvalidateMeasure and InvalidateArrange on the Path (and on the parent grid) to no avail.

The only workaround I've got is to change a property on the path then change it back, which seems to be enough to get the rendering engine to draw the new path elements. I use Opacity. You have to set it to a different value, otherwise (I presume) the PropertyChanged event won't fire. It's a kludge, though.

Has anyone else played with paths in this way? I guess if I were putting other graphical elements on screen at the same time this wouldn't be an issue, so it's probably not something which will affect may people, but it would be good to know if there's a more correct way to do it.

A: 

What kind of element is your layout root? I copied your code and used a Canvas as the layout root and it works great in both IE and Firefox.

<UserControl x:Class="SilverlightTesting.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    Width="400" Height="300">
        <Canvas x:Name="LayoutRoot" Background="White" Width="400" Height="300" MouseLeftButtonDown="LayoutRoot_MouseLeftButtonDown"/>
</UserControl>

I copied all your code and created a new path_LayoutUpdated method which does nothing. When I click on the screen I get new lines draw to the point that I clicked.

Bryant
My original container is a Grid, but Canvas gives me the same effect. It's very odd.
Jim Lynn
A: 

I needed something very similar but drawing on the new Silverlight VE Map Control. Your code above worked fine without any fiddling other properties to 'force a redraw'. Code here for your reference:

using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Microsoft.VirtualEarth.MapControl;

namespace MapInfo
{
    public partial class Page : UserControl
    {
        /// <summary>
        /// Sample drawing a polyline on a Virtual Earth map
        /// </summary>
        public Page()
        {
            InitializeComponent();
            VEMap.MouseLeftButtonUp += new MouseButtonEventHandler(VEMap_MouseLeftButtonDown);
            VEMap.MouseLeave += new MouseEventHandler(VEMap_MouseLeave);
        }

        MapPolyline polyline = null;

        /// <summary>
        /// Ends drawing the current polyline
        /// </summary>
        void VEMap_MouseLeave(object sender, MouseEventArgs e)
        {
            polyline = null;
        }
        /// <summary>
        /// Start or add-to a polyline
        /// </summary>
        private void VEMap_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            Map m = (Map)sender;

            Location l = m.ViewportPointToLocation(e.GetPosition(m));

            if (polyline == null)
            {
                CreateNewPolyline(l);
            }
            else
            {
                polyline.Locations.Add(l);
            }
        }
        /// <summary>
        /// Create a new MapPolyline
        /// </summary>
        /// <param name="startPoint">starting Location</param>
        private void CreateNewPolyline(Location startPoint)
        {
            polyline = new MapPolyline();
            polyline.Stroke = new SolidColorBrush(Colors.Red);
            polyline.StrokeThickness = 2;
            var lc = new LocationCollection();
            lc.Add(startPoint);
            polyline.Locations = lc;
            VEMap.Children.Add(polyline);
        }
    }
}

Doesn't help your immediate scenario but shows that other Silverlight stuff works as you'd expect. Might help someone else I hope...

CraigD
Interesting, although the MapPolyline works in a different way to the standard Polyline element. Internally, every time you add a new location to the collection, it has to rebuild a PointCollection (converting all the locations to scaled element coordinates) which is enough to force the polyline to be updated, I assume.Thanks for the update, though. I ought to try this in Silverlight 3 to see if it behaves differently.
Jim Lynn