Question

I am creating a Signal R app that has a drawing section on the UI, right now I am working on the WPF client but there will eventually be a MVC and Android client as well.

The drawing events are handled perfectly, but with my method for handling erasing the other clients end up erasing entire strokes rather than only the points.

The client doing the erasing is using the EraseByPoint method and everything works fine on his side.

The drawing update details are passed to the clients via the DrawingDto:

public class DrawingDto
{
    public DrawingDto()
    {
        NewStrokes = new List<StrokeDto>();
        DeletedStrokes = new List<StrokeDto>();
    }
    public IList<StrokeDto> NewStrokes { get; set; }
    public IList<StrokeDto> DeletedStrokes { get; set; }
}

public class StrokeDto
{
    public StrokeDto()
    {
        Points = new List<Point>();
    }
    public IList<Point> Points { get; set; }
}

And the code the handles adding new strokes and trying to remove erased ones:

private void OnUpdateDrawing(DrawingDto drawing)
    {
        Execute.OnUiThread(() =>
        {
            (Strokes as INotifyCollectionChanged).CollectionChanged -= StrokesOnCollectionChanged;
            var strokeCollection = new StrokeCollection();
            foreach (var newStroke in drawing.NewStrokes)
            {
                var pointCollection = new StylusPointCollection();
                foreach (var point in newStroke.Points)
                {
                    pointCollection.Add(new StylusPoint(point.X, point.Y));
                }
                strokeCollection.Add(new Stroke(pointCollection));
            }
            Strokes.Add(strokeCollection);

            foreach (var deletedStroke in drawing.DeletedStrokes)
            {
                Strokes.Erase(deletedStroke.Points.Select(x=> new System.Windows.Point(x.X, x.Y)), new RectangleStylusShape(1,1));
            }
            (Strokes as INotifyCollectionChanged).CollectionChanged += StrokesOnCollectionChanged;
        });
    }

In this code Execute.OnUiThread uses the dispatcher to invoke the action, the event listener is being removed to avoid sending recursive updates to the server.

And Finally the code the creates the Dto in the first place:

public async void StrokesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        var dto = new DrawingDto();
        if (args.NewItems !=null)
        {
            foreach (Stroke newStroke in args.NewItems)
            {
                dto.NewStrokes.Add(new StrokeDto { Points = newStroke.StylusPoints.Select(x => new Point((int)x.X, (int)x.Y)).ToList() });
            }
        }

        if (args.OldItems!= null)
        {
            foreach (Stroke deletedStroke in args.OldItems)
            {
                dto.DeletedStrokes.Add(new StrokeDto { Points = deletedStroke.StylusPoints.Select(x => new Point((int)x.X, (int)x.Y)).ToList() });
            }
        }


        await _signalRManager.UpdateDrawing(dto);
    }

Edit: As requested here is the signal r code.

The Signal R Hub:

public class DrawingHub : Hub
{
    public void UpdateDrawing(DrawingDto drawing)
    {
        Clients.Others.UpdateDrawing(drawing);
    }
}

Client side code to send updates to Hub:

public async void StrokesOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
    {
        var dto = new DrawingDto();
        if (args.NewItems !=null)
        {
            foreach (Stroke newStroke in args.NewItems)
            {
                dto.NewStrokes.Add(new StrokeDto { Points = newStroke.StylusPoints.Select(x => new Point((int)x.X, (int)x.Y)).ToList() });
            }
        }

        if (args.OldItems!= null)
        {
            foreach (Stroke deletedStroke in args.OldItems)
            {
                dto.DeletedStrokes.Add(new StrokeDto { Points = deletedStroke.StylusPoints.Select(x => new Point((int)x.X, (int)x.Y)).ToList() });
            }
        }


        await _signalRManager.UpdateDrawing(dto);
    }

Snippet from SignalRManager (this is a singleton class that contains all the proxies):

public async Task UpdateDrawing(DrawingDto dto)
    {
        await DrawingProxy.Invoke("UpdateDrawing", dto);
    }

public event Action<DrawingDto> UpdateDrawingEvent; 


private void OnUpdateDrawing(DrawingDto drawing)
    {
        if(UpdateDrawingEvent != null) UpdateDrawingEvent.Invoke(drawing);
    } 
Was it helpful?

Solution

Ok the problem here was a misunderstanding with how the InkCanvas handles erasing.

Within the StrokesOnCollectionChanged handler the OldItems collection will contain the entirety of any stroke that was touched by the eraser, and NewItems will contain new strokes that are split where the eraser hit the original.

Because of this in the OnUpdateDrawing code by handling the new items first and then erasing the old items I was effectively erasing the new strokes that were just added.

Now by handling the deleted strokes first everything works as expected, the code snippet:

private void OnUpdateDrawing(DrawingDto drawing)
    {
        Execute.OnUiThread(() =>
        {
            (Strokes as INotifyCollectionChanged).CollectionChanged -= StrokesOnCollectionChanged;
            var strokeCollection = new StrokeCollection();

            foreach (var deletedStroke in drawing.DeletedStrokes)
            {
                Strokes.Erase(deletedStroke.Points.Select(x => new System.Windows.Point(x.X, x.Y)), new RectangleStylusShape(0.01, 0.01));
            }

            foreach (var newStroke in drawing.NewStrokes)
            {
                var pointCollection = new StylusPointCollection();
                foreach (var point in newStroke.Points)
                {
                    pointCollection.Add(new StylusPoint(point.X, point.Y));
                }
                strokeCollection.Add(new Stroke(pointCollection));
            }
            Strokes.Add(strokeCollection);

            (Strokes as INotifyCollectionChanged).CollectionChanged += StrokesOnCollectionChanged;
        });
    }

OTHER TIPS

I don't get it, why don't you simply do :

 foreach (var deletedStroke in drawing.DeletedStrokes)
    Strokes.Remove(deletedStroke);

?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top