Search code examples
c#.netwpfxamlinkcanvas

How to synchronize two InkCanvas-es drawings?


I am trying to develop an application which shows WPF InkCanvas drawings on remote host. Basically it synchronizes local InkCanvas with several remote hosts. I have subscribed to StrokesChanged event:

        this.DrawingCanvas.Strokes.StrokesChanged += this.Strokes_StrokesChanged;

And the handler.

    private void Strokes_StrokesChanged(object sender, StrokeCollectionChangedEventArgs e)
    {
        if (e.Added != null && e.Added.Count > 0)
        {
            this.StrokeSynchronizer.SendAddedStroke(e.Added);
        }

        if (e.Removed != null && e.Removed.Count > 0)
        {
            this.StrokeSynchronizer.SendRemovedStroke(e.Removed);
        }
    }

When I am drawing a new curve the event invoked only once. The remote host draws it correctly by calling this.RemoteInkCanvas.Strokes.Add(addedStrokes).

When I am erasing a curve via InkCanvasEditingMode.EraseByStroke the event also invoked once and remote host uses this.RemoteInkCanvas.Strokes.Remove(removedStrokes) successfully.

Here goes the problem!

When the this.DrawingCanvas.EditingMode is InkCanvasEditingMode.EraseByPoint then the event invoked once but with two collections (Added and Removed). This causes remote hosts to get insane. Here is the remote host code which erases strokes:

    private StrokeCollection FindStrokesInLocalCollection(StrokeCollection receivedCollection)
    {
        var localStrokes = new StrokeCollection();
        foreach (var erasedStroke in receivedCollection)
        {
            var erasedPoints = erasedStroke.StylusPoints;
            foreach (var existentStoke in this.RemoteInkCanvas.Strokes)
            {
                var existentPoints = existentStoke.StylusPoints;
                if (erasedPoints.SequenceEqual(existentPoints))
                {
                    localStrokes.Add(existentStoke);
                }
            }
        }

        return localStrokes;
    }

    private void RemoteStrokeRemoved(StrokeCollection strokes)
    {
        try
        {
            // Simple this.RemoteInkCanvas.Strokes.Remove(strokes)
            // does not work, because local and remote strokes are different (though equal) objects.
            // Thus we need to find same strokes in local collection.
            var strokesToRemove = this.FindStrokesInLocalCollection(strokes);

            if (strokesToRemove.Count != strokes.Count)
            {
                Logger.Warn(string.Format(
                    "Whiteboard: Seems like remotely removed strokes were not found in local whiteboard. Remote count {0}, local count {1}.",
                    strokes.Count,
                    strokesToRemove.Count));
            }

            this.RemoteInkCanvas.Strokes.Remove(strokesToRemove);
        }
        catch (Exception ex)
        {
            Logger.Error("Whiteboard: Can not remove some strokes received from remote host.", ex);
        }
    }

Please note, that the exception is always being caught.

The general question here: how to find same strokes/points in the collection in order to remove them accordingly?


Solution

  • The problem is in serialization/deserialization mechanism used by StrokesCollection. When a double value is serialized and deserialized the resulting value slightly varies.

    The complete code sample and answer can be found here: http://social.msdn.microsoft.com/Forums/en-AU/wpf/thread/9e1f43fa-6266-41b7-a5d0-7603f87ca58f