Oh, I'm making my tilemap editor too
In my map class I use a filestream for load and save purposes. You will need to create your own file format, where you will store all map infos. A simpler approach could be using a text file to store your data.

[pascal]
// saving function
var
check, mapVer: array[0..2] of char;
...
f := TFileStream.Create(filename, fmCreate);
try
f.Seek(0, soFromBeginning);

check := 'emg'; // For control check when load the map
mapVer := '001';// Version number

F.Write(check, sizeof(check)); // I'm writing control check chars
F.Write(mapVer, sizeof(mapVer)); // version number
F.Write(mapName, sizeof(mapName)); // map name: string[255]
F.Write(mapDesc, sizeof(mapDesc)); //map description: string[4000]
F.Write(tileSet, sizeof(tileSet)); // tileset file name: string[255]
F.Write(TileSize, SizeOf(integer)); // tile size in pixel (eg. 16): integer;
F.Write(HorizTiles, SizeOf(integer)); //map width in tiles: integer
F.Write(VertTiles, SizeOf(integer)); //map height in tiles: integer
F.Write(LayerCount, SizeOf(integer)); //num of layers: integer

for a := 0 to LayerCount - 1 do
begin
F.Write(Layer[a].Name, SizeOf(Layer[a].name));
for y := 0 to VertTiles - 1 do
for x := 0 to HorizTiles - 1 do
begin
F.Write(Layer[a].Tiles[x, y].ImgIndex, SizeOf(integer));
F.Write(Layer[a].Tiles[x, y].collision, SizeOf(integer));
end;
end;
f.Seek(0, soFromEnd);
Result := true;
finally
f.Free;
end;
...

// Loading
f := TFileStream.Create(filename, fmOpenRead);
try
f.Read(check, SizeOf(check));
if check <> 'emg' then
begin
MessageDlg('Invalid .emg file.', mtError, [mbOK], 0);
exit;
end;

f.Read(mapVer, SizeOf(mapVer));
f.Read(mapName, sizeof(mapName));
f.Read(mapDesc, sizeof(mapDesc));
f.Read(tileSet, sizeof(tileSet));
f.Read(TileSize, SizeOf(integer));
f.Read(HorizTiles, SizeOf(integer));
f.Read(VertTiles, SizeOf(integer));
F.Read(LayerCount, SizeOf(integer));

SetLength(Layer, LayerCount);
for i := 0 to LayerCount - 1 do
begin
SetLength(Layer[i].tiles, HorizTiles, VertTiles);
end;

for a := 0 to LayerCount - 1 do
begin
f.Read(Layer[a].name, SizeOf(Layer[a].name));
for y := 0 to VertTiles - 1 do
for x := 0 to HorizTiles - 1 do
begin
f.Read(Layer[a].Tiles[x, y].ImgIndex, SizeOf(integer));
f.Read(Layer[a].Tiles[x, y].collision, SizeOf(integer));
end;
end;
f.Seek(0, soFromEnd);
Result := true;
finally
f.Free;
end;
[/pascal]

Well, something like that. You will need some adjustments, of course


About undo, I have an Undo record like that:

[pascal]
TUndoRecord = record
posX, posY: Integer;
Layer: Integer;
Tile: Integer;
Collision: Integer;
end;
PUndoRecord = ^TUndoRecord;
[/pascal]

and a TList where I store the state of the tile I will change. In order to undo changes, all you need is to get the last item in TList, restore its values and remove it from TList.
Hope it helps