[Rod Stephens Books]
Index Books Python Examples About Rod Contact
[Mastodon] [Bluesky]
[Build Your Own Ray Tracer With Python]

[Beginning Database Design Solutions, Second Edition]

[Beginning Software Engineering, Second Edition]

[Essential Algorithms, Second Edition]

[The Modern C# Challenge]

[WPF 3d, Three-Dimensional Graphics with WPF and C#]

[The C# Helper Top 100]

[Interview Puzzles Dissected]

Title: Save and restore objects drawn by the scribble program in Python and tkinter

[This program saves and restores objects drawn by the scribble program in Python and tkinter.]

There are several ways that you can save and restore objects. Python has the built-in pickle tool, which saves and restores objects in a binary format. Three big disadvantages of that format are (1) its binary so you can't see what's inside and make changes if needed, (2) it's incompatible with non-Python programs that can't use pickle, and (3) unpickling an unsafe file can execute malicious code.

Two other popular options are XML (eXtensible Markup Language) files and JSON (JavaScript Object Notation) files. XML is more Microsoft-oriented and perhaps better for complex document structures that require validation. XML also has some vulnerabilities with unlikely names like billion laughs and decompression bomb.

JSON is generally more concise, seems secure (as long as you use the standard JSON Python library) and seems to be more popular, so I decided to use JSON.

Serialization

Serialization is the process of converting objects into a serial stream, in this case, a text stream. You can then do what you like with that text such as display it, send it across a network, or save it into a file (which is what we will do).

The bad news is the json library only knows how to serialize and deserialize a limited number of object types. The good news is it's fairly easy to add a little code to serialize and deserialize many other object types.

The json library cannot serialize the shapes drawn on a canvas widget, but it can serialize a list. This code makes a list of dictionary objects, each of which represents a shape. The library can then serialize it.

The following code shows the serialize_canvas method used by this example.

import tkinter as tk import json def serialize_canvas(canvas): items = [] for item in canvas.find_all(): item_data = { 'type': canvas.type(item), 'coords': canvas.coords(item), 'options': {} } for option in canvas.itemconfig(item): value = canvas.itemcget(item, option) if value != '': item_data['options'][option] = value items.append(item_data) # Return the serialized data. return json.dumps(items, indent=4)

This code creates an empty items list and then loops through the objects on the canvas widget.

For each object, the code creates a dictionary named item_data to hold the object's type, coords value, and an options dictionary. It then loops through the object's options and adds their names and values to the options dictionary.

After saving the object's options, the code appends the object's item_data dictionary to the items list.

After it has processed all of the items, the method calls json.dumps to serialize the items list into a string. It uses the parameter indent=4 to make the serialization use newlines and indentation to produce a pretty result.

Here's a sample holding a single dashed oval. (The file gets really long if you include a line so this is example is very simple.)

[ { "type": "oval", "coords": [ 38.0, 32.0, 351.0, 211.0 ], "options": { "activewidth": "0.0", "dash": "6 4", "dashoffset": "0", "disabledwidth": "0.0", "offset": "0,0", "outline": "#ff00ff", "outlineoffset": "0,0", "width": "5.0" } } ]

This example uses the following code to serialize its drawing objects into a string and then write that string into a file.

json_data = serialize_canvas(self.canvas) with open(filename, 'w', encoding='utf-8') as f: f.write(json_data)

Deserialization

Deserialization is the process of converting a serialization back into objects. The following code shows how the program deserializes a serialization.

def deserialize_canvas(canvas, data): # Delete any existing items. canvas.delete(tk.ALL) # Deserialize the JSON data. items = json.loads(data) # Recreate the items. for item in items: coords = item['coords'] options = item['options'] if item['type'] == 'line': canvas.create_line(*coords, **options) elif item['type'] == 'oval': canvas.create_oval(*coords, **options)

This code deletes any existing object from the program's canvas and then uses json.load to deserialize the string data. The result is a list of dictionary objects, each representing a drawing object.

The code then loops through the object dictionaries. For each, it gets the object's coords and options values. It then uses a series of if statements to decide what kind of shape this is and uses the appropriate canvas method to create the object. The remarkable thing about this is that the program really only need to know the object's coords and options values to create the object.

Download the example to see all of the details.

© 2024 Rocky Mountain Computer Consulting, Inc. All rights reserved.