Michał Wyszyński
Michał Wyszyński

Reputation: 13

Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type System.Windows.Ink.Stroke

I'm trying to save strokes and children from a WPF InkCanvas. I managed to get the file to save, but now I can't load it. It throws this exception:

Newtonsoft.Json.JsonSerializationException HResult=0x80131500 Message=Unable to find a constructor to use for type System.Windows.Ink.Stroke. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'strokes[0].DrawingAttributes', line 4, position 26. Source=Newtonsoft.Json

Code for save/load classes:

class SaveLoad
{
    public static void Save(Board board) {
        Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
        dlg.FileName = "Document";
        dlg.DefaultExt = ".txt";
        dlg.Filter = "Text documents (.txt)|*.txt";
        Nullable<bool> result = dlg.ShowDialog();
        if (result == true)
        {
            string outpath = dlg.FileName;
            string jsonData = JsonConvert.SerializeObject(board, Newtonsoft.Json.Formatting.Indented);
            var myFile = File.Create(outpath);
            myFile.Close();
            File.WriteAllText(@"" + outpath, jsonData);
        }
    }

    public static Board Load()
    {
        Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
        dlg.FileName = "Document";
        dlg.DefaultExt = ".txt";
        dlg.Filter = "Text documents (.txt)|*.txt";
        Nullable<bool> result = dlg.ShowDialog();
        if (result == true)
        {
            string outpath = dlg.FileName;
            string jsonData = File.ReadAllText(outpath);
            return JsonConvert.DeserializeObject<Board>(jsonData);
        }
        else { return null; }
    }
}

internal class Board
{
    public StrokeCollection strokes;
    public UIElementCollection elements;
    public UIElementCollection elements_serialized = new UIElementCollection(new UIElement(),new FrameworkElement());

    public Board(StrokeCollection strokes, UIElementCollection elements) { this.strokes = strokes; this.elements = elements; }

    public void Serialize() {
        foreach (UIElement element in this.elements) {
            elements_serialized.Add(Clone(element));
        }
    }

    public Board getSerialized() {
        return new Board(strokes,elements_serialized);
    }

    public static T Clone<T>(T element)
    {
        string xaml = XamlWriter.Save(element);
        using (StringReader stringReader = new StringReader(xaml))
        using (XmlReader xmlReader = XmlReader.Create(stringReader))
            return (T)XamlReader.Load(xmlReader);
    }
}

Code for saving:

Board board = new Board(mainInk.Strokes, mainInk.Children);
board.Serialize();
SaveLoad.Save(board.getSerialized());

Code for loading:

 Board board = SaveLoad.Load();
 if (board == null) {
     mainInk.Strokes = board.strokes;
     mainInk.Children.Clear();
     foreach (UIElement element in board.elements)
     {
         mainInk.Children.Add(Board.Clone(element));
     }
 }

Upvotes: 0

Views: 56

Answers (2)

SELA
SELA

Reputation: 6793

As @Migue Ang Rdz said you create a custom converter for the StrokeCollection or Stroke classes to handle their serialization and deserialization.

Refer the code below for more reference:

Create a class like below:

public class StrokeConverter : JsonConverter<Stroke>
{
    public override void WriteJson(JsonWriter writer, Stroke value, JsonSerializer serializer)
    {
        var data = new
        {
            Points = value.StylusPoints,
            DrawingAttributes = value.DrawingAttributes
        };
        serializer.Serialize(writer, data);
    }

    public override Stroke ReadJson(JsonReader reader, Type objectType, Stroke existingValue, bool hasExistingValue, JsonSerializer serializer)
    {
        var data = serializer.Deserialize<dynamic>(reader);
        var points = ((JArray)data["Points"]).ToObject<StylusPointCollection>();
        var attributes = ((JObject)data["DrawingAttributes"]).ToObject<DrawingAttributes>();
        return new Stroke(points) { DrawingAttributes = attributes };
    }
}

Then register above and start using it like below:

var settings = new JsonSerializerSettings();
settings.Converters.Add(new StrokeConverter()); // Register the converter
var jsonData = JsonConvert.SerializeObject(board, Formatting.Indented, settings); 

Upvotes: 0

Migue Ang Rdz
Migue Ang Rdz

Reputation: 96

Implement a custom converter that handles the serialization and deserialization of the Stroke object. since this class does not have a constructor parameter less.

public class StrokeConverter : JsonConverter
{
//overrides here
}

Upvotes: 0

Related Questions