Wobbles
Wobbles

Reputation: 3135

Using a JsonConverter to serialize/deserialize various `Brush` types

So after reading various other questions and answers regarding Json.NET's inability to (de)serialize Brush types, it was apparent that I needed my own JsonConverter. However where I am stuck is I am using a go-between object to handle the data as it is (de)serialized and since there are various typed objects depending on the brush type, I assume I need to store the type information in Json aswell, however using a [JsonProperty( TypeNameHandling = TypeNameHandling.All )] is not working.

My converter:

public class BrushJsonConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        object SerializableBrush = null;
        if (value is SolidColorBrush)
            SerializableBrush = new SerializableColorBrush(value as SolidColorBrush);

        var jo = JObject.FromObject(SerializableBrush);
        jo.WriteTo(writer);
    }

    public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
        JsonSerializer serializer)
    {
        // Load JObject from stream
        var jObject = JObject.Load(reader);

        dynamic SerializableBrush = JsonConvert.DeserializeObject(jObject.ToString());

        if (SerializableBrush is SerializableColorBrush)
            return (SerializableBrush as SerializableColorBrush).ToBrush();
        else if (SerializableBrush is SerializableImageBrush)
            return (SerializableBrush as SerializableImageBrush).ToBrush();
        else return null;

    }

    public override bool CanConvert(Type objectType)
    {
        return typeof(Brush).IsAssignableFrom(objectType);
    }
}

My Go-between objects:

class SerializableColorBrush
{
    public Color Color{ get; set; }

    public SerializableColorBrush(SolidColorBrush Brush)
    {
        this.Color = Brush.Color;
    }

    public SolidColorBrush ToBrush()
    {
        SolidColorBrush brush = new SolidColorBrush(this.Color);
        return brush;
    }
}
class SerializableImageBrush
{
    public ImageSource ImageSource { get; set; }
    public TileMode TileMode { get; set; }
    public Stretch Stretch { get; set; }
    public AlignmentX AlignmentX { get; set; }
    public AlignmentY AlignmentY { get; set; }

    public SerializableImageBrush(ImageBrush Brush)
    {
        this.ImageSource = Brush.ImageSource;
        this.TileMode = Brush.TileMode;
        this.Stretch = Brush.Stretch;
        this.AlignmentX = Brush.AlignmentX;
        this.AlignmentY = Brush.AlignmentY;
    }

    public ImageBrush ToBrush()
    {
        ImageBrush brush = new ImageBrush();
        brush.ImageSource = ImageSource;
        brush.TileMode = TileMode;
        brush.Stretch = Stretch;
        brush.AlignmentX = AlignmentX;
        brush.AlignmentY = AlignmentY;
        return brush;
    }
}

An example property to be serialized:

    private Brush _WindowBG = SystemColors.AppWorkspaceBrush;
    [JsonConverter(typeof(BrushJsonConverter))]
    [JsonProperty( TypeNameHandling = TypeNameHandling.All )]
    public Brush WindowBG { get { return _WindowBG; } set { if (value != _WindowBG) { _WindowBG = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("WindowBG")); } } }

Where have I gone astray? And am I even going in the right direction with this method?

End result is I am trying to store theming data for my application into json and have the various brushes be either color, image, or gradient (not yet included).

Upvotes: 3

Views: 791

Answers (1)

Wobbles
Wobbles

Reputation: 3135

Ended up going a different route, which IMHO is a lot cleaner of an implementation.

Kept the objects that used to be my go-between objects, but they are now the objects that I actually store the data using and they (for ease of use) inherit a base class;

public class SerializableBrush
{
    public virtual Brush ToBrush()
    {
        return null;
    }
}

public class SerializableColorBrush : SerializableBrush
{
    public Color Color{ get; set; }

    public SerializableColorBrush(SolidColorBrush Brush)
    {
        this.Color = Brush.Color;
    }

    public override Brush ToBrush()
    {
        SolidColorBrush brush = new SolidColorBrush(this.Color);
        return brush;
    }
}
public class SerializableImageBrush : SerializableBrush
{
    public ImageSource ImageSource { get; set; }
    public TileMode TileMode { get; set; }
    public Stretch Stretch { get; set; }
    public AlignmentX AlignmentX { get; set; }
    public AlignmentY AlignmentY { get; set; }

    public SerializableImageBrush(ImageBrush Brush)
    {
        this.ImageSource = Brush.ImageSource;
        this.TileMode = Brush.TileMode;
        this.Stretch = Brush.Stretch;
        this.AlignmentX = Brush.AlignmentX;
        this.AlignmentY = Brush.AlignmentY;
    }

    public override Brush ToBrush()
    {
        ImageBrush brush = new ImageBrush();
        brush.ImageSource = ImageSource;
        brush.TileMode = TileMode;
        brush.Stretch = Stretch;
        brush.AlignmentX = AlignmentX;
        brush.AlignmentY = AlignmentY;
        return brush;
    }
}

Then instead of binding the objects directly to a brush in XAML, I created a converter that turned my custom generic objects into brush's (this is where the base class makes things really easy);

public class SerializableBrushToBrush : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return (value as Config.SerializableBrush).ToBrush();
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

Of course my template class that stored the brush info needed to use my new types, I also added a OnDeserialized callback as my template would not show if I populated the Json after the main window showed;

public class Template : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private SerializableBrush _WindowBG = new SerializableColorBrush(SystemColors.AppWorkspaceBrush);
    [JsonProperty(TypeNameHandling = TypeNameHandling.All)]
    public SerializableBrush WindowBG { get { return _WindowBG; } set { if (value != _WindowBG) { _WindowBG = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("WindowBG")); } } }

    [OnDeserialized]
    internal void OnDeserializedMethod(StreamingContext context)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(string.Empty));
    }

}

And of course finally my window needed to use the new converter;

<Window.Resources>
    <local:SerializableBrushToBrush x:Key="SerializableBrushToBrush"/>
</Window.Resources>
<Window.Background>
    <Binding Converter="{StaticResource SerializableBrushToBrush}" Path="Template.WindowBG" Source="{x:Static config:Global.store}"/>
</Window.Background>

Now my Json looks nice and clean and easy to manipulate

  "Template": {
    "WindowBG": {
      "$type": "Config.SerializableImageBrush, Config",
      "ImageSource": "C:\\Users\\jhebb\\Pictures\\20150805_150241.jpg",
      "TileMode": 0,
      "Stretch": 1,
      "AlignmentX": 1,
      "AlignmentY": 1
    },
    "WindowFG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FF000000"
    },
    "DeviceBarBG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FFA0A0A0"
    },
    "DeviceBarFG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FF000000"
    },
    "WorkspaceBG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FFFFFFFF"
    },
    "MainTabFG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FF000000"
    },
    "MenuBG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FFF0F0F0"
    },
    "MenuFG": {
      "$type": "Config.SerializableColorBrush, Config",
      "Color": "#FF000000"
    }
  }

Upvotes: 2

Related Questions