I am looking for a way to fast and simple implementation of this paradigm:
MyByteArray mb = new MyByteArray();
mb.Add<byte>(bytevalue);
mb.Add<float>(floatvalue);
mb.Add<string>(str);
mb.Add<MyClass>(object);
And then get byte[] from mb to send it as a byte packet via RPC call (to be decoded on the other side using the same technique).
I've found MemoryStream, but it looks like too overheaded for this simple operation.
Can you help me? Thank you.
What are you looking for is BinaryWritter. But it still needs a Stream to write on for pure logic reason. And the only Stream that fits in your need is MemoryStream.
Are you afraid of performance overhead ? You can create your MemoryStream from an existing byte array ;
byte [] buffer = new byte[1024];
using (var memoryStream = new MemoryStream(buffer))
{
using (var binaryWriter = new BinaryWriter(memoryStream))
{
binaryWriter.Write(1.2F); // float
binaryWriter.Write(1.9D); // double
binaryWriter.Write(1); // integer
binaryWriter.Write("str"); // string
}
}
// buffer is filled with your data now.
A tricky way to achieve this is to use a combination of builtin class in .net
class Program
{
static void Main(string[] args)
{
Program program = new Program();
var listBytes = new List<byte>();
listBytes.Add( program.CastToBytes("test"));
listBytes.Add(program.CastToBytes(5));
}
Note
for a custom object you have to define an implicit operator on how the properties or all the object should be converted
public byte[] CastToBytes<T>(T value)
{
//this will cover most of primitive types
if (typeof(T).IsValueType)
{
return BitConverter.GetBytes((dynamic)value);
}
if (typeof(T) == typeof(string))
{
return Encoding.UTF8.GetBytes((dynamic) value);
}
//for a custom object you have to define the rules
else
{
var formatter = new BinaryFormatter();
var memoryStream = new MemoryStream();
formatter.Serialize(memoryStream, value);
return memoryStream.GetBuffer();
}
}
}
This looks like the case for Protocol Buffers, you could look at at protobuf-net.
First, let's decorate the classes.
[ProtoContract]
class User
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public string Name { get; set; }
}
[ProtoContract]
class Message
{
[ProtoMember(1)]
public byte Type { get; set; }
[ProtoMember(2)]
public float Value { get; set; }
[ProtoMember(3)]
public User Sender { get; set; }
}
Then we create our message.
var msg = new Message
{
Type = 1,
Value = 1.1f,
Sender = new User
{
Id = 8,
Name = "user"
}
};
And now, we can use ProtoBuf's serializer to do all our work.
// memory
using (var mem = new MemoryStream())
{
Serializer.Serialize<Message>(mem, msg);
var bytes = mem.GetBuffer();
}
// file
using (var file = File.Create("message.bin")) Serializer.Serialize<Message>(file, msg);
Related
I'm pretty new to C# and I have to handle a byte stream that I receive. In C++ I usually used something like that:
#pragma pack(push, DTA_VLS, 1)
typedef struct tsHEADER
{
WORD wLength;
WORD wIdCounter;
WORD wxxxx;
WORD wxxxx2;
} tHEADER;
#pragma pack(pop, DTA_VLS)
and then when I received a byte array I could do something like that:
tHEADER header*;
header = receivedByteArray;
if(header->wLength >0)
{
do something
}
Is there something similar I could do in C# when I want to read a received telegram or create a new one? Or can I do only something like that:
byte[] Tel= new byte(5);
byte[0]= Length;
byte[1]=ID;
// and so on
I think the best thing you can do is use the BinaryWriter and then make a small class to hold the data. You can also use BinaryReader to read the bytes back in. There is a BinaryFormatter, but I am pretty sure that has extra meta data in it though (don't hold me to that).
public class MyPackage
{
public int Length => 12; // or 16 if Length is included
public int Id { get; set; }
public int Number1 { get; set; }
public int Number2 { get; set; }
public byte[] SerializeToBinary()
{
using (var memoryStream = new MemoryStream())
using (var writer = new BinaryWriter(memoryStream))
{
writer.Write(Length);
writer.Write(Id);
writer.Write(Number1);
writer.Write(Number2);
return memoryStream.ToArray();
}
}
public void DeserializeFromBinary(byte[] data)
{
using(var memoryStream = new MemoryStream(data))
using(var reader = new BinaryReader(memoryStream))
{
var length = reader.ReadInt32();
if (length != Length)
throw new Exception("Some error if you care about length");
Id = reader.ReadInt32();
Number1 = reader.ReadInt32();
Number2 = reader.ReadInt32();
}
}
}
Previously, we serialized a property as a List<byte>
Now we want to change it to be a byte[].
It was out understanding that you should be able to swap out collection types freely between version but we get a ProtoBuf.ProtoException
[TestFixture, Category("Framework")]
class CollectionTypeChange
{
[Test]
public void TestRoundTrip()
{
var bytes = new List<byte>() {1,2,4};
var a = new ArrayHolder(bytes);
var aCopy = Deserialize<ArrayHolder>(Serialize(a));
//Passes
Assert.That(aCopy.CollectionOfBytes, Is.EquivalentTo(a.CollectionOfBytes));
}
[Test]
public void TestChangeArrayToList()
{
var bytes = new List<byte>() { 1, 2, 4 };
var a = new ArrayHolder(bytes);
var aCopy = Deserialize<ListHolder>(Serialize(a));
//Passes
Assert.That(aCopy.CollectionOfBytes, Is.EquivalentTo(a.CollectionOfBytes));
}
[Test]
public void TestChangeListToArray()
{
var bytes = new List<byte>() { 1, 2, 4 };
var a = new ListHolder(bytes);
//Throws: ProtoBuf.ProtoException : Invalid wire-type; this usually means you have over-written a file without truncating or setting the length; see http://stackoverflow.com/q/2152978/23354
var aCopy = Deserialize<ArrayHolder>(Serialize(a));
Assert.That(aCopy.CollectionOfBytes, Is.EquivalentTo(a.CollectionOfBytes));
}
public static byte[] Serialize<T>(T obj)
{
using (var stream = new MemoryStream())
{
Serializer.Serialize(stream, obj);
return stream.ToArray();
}
}
public static T Deserialize<T>(byte[] buffer)
{
using (var stream = new MemoryStream(buffer))
{
return Serializer.Deserialize<T>(stream);
}
}
}
[ProtoContract]
internal class ArrayHolder
{
private ArrayHolder()
{
CollectionOfBytes = new byte[0] {};
}
internal ArrayHolder(IEnumerable<byte> bytesToUse )
{
CollectionOfBytes = bytesToUse.ToArray();
}
[ProtoMember(1)]
public byte[] CollectionOfBytes { get; set; }
}
[ProtoContract]
internal class ListHolder
{
private ListHolder()
{
CollectionOfBytes = new List<byte>();
}
internal ListHolder(IEnumerable<byte> bytesToUse)
{
CollectionOfBytes = bytesToUse.ToList();
}
[ProtoMember(1)]
public List<byte> CollectionOfBytes { get; set; }
}
Is there a special thing about arrays, or bytes that means this doesn't work like we expected?
This looks to be a problem specifically with byte[] properties. If I change the property types to int [] and List<int> the behavior is not reproducible. The problem arises from the fact that there are two ways to encode an array in a Protocol Buffer: as repeated key/value pairs or "packed" as a single key with a length-delimited block of values.
For byte arrays, protobuf-net uses a special serializer, BlobSerializer, which simply writes the byte array length then block-copies the contents into the output buffer as a packed repeated field. It does the reverse operation when reading -- not handling the case when the data is actually in repeated key/value format.
On the other hand, List<byte> is serialized using the general-purpose ListDecorator. Its Read() method tests to see the format currently in the input buffer and reads it appropriately -- either packed or unpacked. Its Write() method, however, writes the byte array unpacked by default. Subsequently, when reading the buffer into a byte [] array, BlobSerializer throws an exception because the format is not as expected. Arguably this is a bug with protobuf-net's BlobSerializer.
There is, however, a straightforward workaround: state that the List<byte> should be serialized in packed format by setting IsPacked = true:
[ProtoContract]
internal class ListHolder
{
private ListHolder()
{
CollectionOfBytes = new List<byte>();
}
internal ListHolder(IEnumerable<byte> bytesToUse)
{
CollectionOfBytes = bytesToUse.ToList();
}
[ProtoMember(1, IsPacked = true)]
public List<byte> CollectionOfBytes { get; set; }
}
This should be a more compact representation for your list of bytes as well.
Unfortunately, the above workaround fails when the byte collection contains bytes with the high bit set. Protobuf-net serializes a packed List<byte> as a length-delimited sequence of Base 128 Varints. Thus when a byte with its high bit set is serialized, it is encoded as two bytes. On the other hand a byte [] member is serialized like a string as a length-delimited sequence of raw bytes. Thus one byte in the byte array is always encoded as byte in the encoding - which is incompatible with the encoding for List<byte>.
As a workaround, one could use a private surrogate List<byte> property in the ArrayHolder type:
[ProtoContract]
internal class ArrayHolder
{
private ArrayHolder()
{
CollectionOfBytes = new byte[0] { };
}
internal ArrayHolder(IEnumerable<byte> bytesToUse)
{
CollectionOfBytes = bytesToUse.ToArray();
}
[ProtoIgnore]
public byte[] CollectionOfBytes { get; set; }
[ProtoMember(1, OverwriteList = true)]
List<byte> ListOfBytes
{
get
{
if (CollectionOfBytes == null)
return null;
return new List<byte>(CollectionOfBytes);
}
set
{
if (value == null)
return;
CollectionOfBytes = value.ToArray();
}
}
}
Sample fiddle.
Alternatively, one could replace the ArrayHolder with a ListHolder during (de)serialization by using MetaType.SetSurrogate() as shown for instance in this answer.
I have an object that I want to serialize into byte[] in order to save it or move it around. Let's assume it's:
public class Message
{
public MessageType MessageType { get; set; }
public byte[] Body { get; set; }
}
public enum MessageType : byte
{
Type1 = 1,
Type2 = 2,
}
My code is a .Net 4.5, Windows 8.1 and Windows Phone 8.1 PCL (Portable Class Library), and all the libraries, examples, and answers I came across don't work with PCL, mostly because of their use of BinaryFormatter, which is not available in PCL.
So, how to go about it?
With a class that simple just write your own serializer using BinaryWriter and BinaryReader (Both useable in PCL projects)
public class Message
{
public MessageType MessageType { get; set; }
public byte[] Body { get; set; }
public byte[] Serialize()
{
//Data will be serialized in the format
// - MessageType (1 byte)
// - BodyLength (4 bytes)
// - Body (x Bytes)
//We allocate a fixed buffer as we know the size already.
var buffer = new byte[Body.Length + 5];
using(var ms = new MemoryStream(buffer)
{
Serialize(ms);
}
//Return our buffer.
return buffer
}
//Just in case you have a stream instead of a byte[]
public void Serialize(Stream stream)
{
using(var writer = new BinaryWriter(stream, Encoding.UTF8, true))
{
writer.Write((byte)this.MessageType);
writer.Write(Body.Length);
writer.Write(Body);
}
}
public static Message Deserialize(byte[] data)
{
using(var ms = new MemoryStream(data))
{
return Message.Deserialize(ms);
}
}
//Just in case you have a stream instead of a byte[]
public static Message Deserialize(Stream data)
{
var message = new Message();
//Use the default text encoding (does not matter for us) and leave the stream open.
using(var reader = new BinaryReader(data, Encoding.UTF8, true))
{
//We do the same order of operations.
message.MessageType = (MessageType)reader.ReadByte();
var bodyLength = reader.ReadInt32();
message.Body = reader.ReadBytes(bodyLength);
}
return message;
}
}
Here is a simplified version if you are never going to use Streams for deserializing.
public byte[] Serialize()
{
//Data will be serialized in the format
// - MessageType (1 byte)
// - Body (x Bytes)
//We allocate a fixed buffer as we know the size already.
var data = new byte[Body.Length + 1];
data[0] = (byte)this.MessageType;
//We copy the data from Body in to data starting at index 1 in data.
Array.Copy(Body, 0, data, 1, Body.Length);
return data;
}
public static Message Deserialize(byte[] data)
{
var message = new Message();
//We do the same order of operations.
message.MessageType = (MessageType)data[0];
//Create a new array and copy the body data in to it.
var body = new byte[data.Length - 1];
Array.Copy(data, 1, body, 0, data.Length - 1);
//Assign the body array to the property.
message.Body = body;
return message;
}
}
I have a client server application in which I need to transmit a user defined object from Client to Server using TCP connection. My object is of the following structure:
class Conversation
{
private string convName, convOwner;
public ArrayList convUsers;
public string getConvName()
{
return this.convName;
}
public string getConvOwner()
{
return this.convOwner;
}
}
Please help me how to transmit this object at from client and again de-serialize it into appropriate object at server side.
As answered, you should make your object serializable. Once you did that with the Serializable attribute, you can use the famous BinaryFormatter to convert your object into a byte array.
You can find many examples out there for using the BinaryFormatter, just use your favorite search engine. Here's a short example:
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
public class SerializationUtils
{
public static byte[] SerializeToByteArray(object request)
{
byte[] result;
BinaryFormatter serializer = new BinaryFormatter();
using (MemoryStream memStream = new MemoryStream())
{
serializer.Serialize(memStream, request);
result = memStream.GetBuffer();
}
return result;
}
public static T DeserializeFromByteArray<T>(byte[] buffer)
{
BinaryFormatter deserializer = new BinaryFormatter();
using (MemoryStream memStream = new MemoryStream(buffer))
{
object newobj = deserializer.Deserialize(memStream);
return (T)newobj;
}
}
}
As for your class, it includes two private fields. I can't see where you set values for them, so I changed your code a bit, so that they can be set in the constructor. In addition, I added the needed Serializable attribute:
using System;
using System.Collections;
[Serializable]
public class Conversation
{
public Conversation(string convName, string convOwner)
{
this.convName = convName;
this.convOwner = convOwner;
}
public Conversation()
{
}
private string convName, convOwner;
public ArrayList convUsers;
public string getConvName()
{
return this.convName;
}
public string getConvOwner()
{
return this.convOwner;
}
}
Now let's put it all together, and see your class serialized and then deserialized, in a Console Application:
using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
namespace Capishi
{
[Serializable]
public class Conversation
{
public Conversation(string convName, string convOwner)
{
this.convName = convName;
this.convOwner = convOwner;
}
public Conversation()
{
}
private string convName, convOwner;
public ArrayList convUsers;
public string getConvName()
{
return this.convName;
}
public string getConvOwner()
{
return this.convOwner;
}
}
public class SerializationUtils
{
public static byte[] SerializeToByteArray(object request)
{
byte[] result;
BinaryFormatter serializer = new BinaryFormatter();
using (MemoryStream memStream = new MemoryStream())
{
serializer.Serialize(memStream, request);
result = memStream.GetBuffer();
}
return result;
}
public static T DeserializeFromByteArray<T>(byte[] buffer)
{
BinaryFormatter deserializer = new BinaryFormatter();
using (MemoryStream memStream = new MemoryStream(buffer))
{
object newobj = deserializer.Deserialize(memStream);
return (T)newobj;
}
}
}
class Program
{
static void Main(string[] args)
{
// create and initialize a conversation object
var convName = "Capishi";
var convOwner = "Ice Cream";
Conversation myConversation = new Conversation(convName, convOwner);
myConversation.convUsers = new ArrayList();
myConversation.convUsers.Add("Ron Klein");
myConversation.convUsers.Add("Rakesh K");
// serialize to a byte array
byte[] data = SerializationUtils.SerializeToByteArray(myConversation);
// print the resulting byte array if you want
// PrintArray(data);
// deserialize the object (on the other side of the communication
Conversation otherConversation = SerializationUtils.DeserializeFromByteArray<Conversation>(data);
// let's see if all of the members are really there
Console.WriteLine("*** start output ***");
Console.WriteLine("otherConversation.getConvName() = " + otherConversation.getConvName());
Console.WriteLine("otherConversation.getConvOwner() = " + otherConversation.getConvOwner());
Console.WriteLine("otherConversation.convUsers:");
foreach (object item in otherConversation.convUsers)
{
Console.WriteLine(item);
}
Console.WriteLine("*** done output ***");
// wait before close
Console.ReadLine();
}
/// <summary>
/// just a helper function to dump an array to the console's output
/// </summary>
/// <param name="data"></param>
private static void PrintArray(byte[] data)
{
for (int i = 0; i < data.Length; i++)
{
Console.Write("{0:000}", data[i]);
if (i < data.Length - 1)
Console.Write(", ");
}
Console.WriteLine();
}
}
}
The result is:
*** start output ***
otherConversation.getConvName() = Capishi
otherConversation.getConvOwner() = Ice Cream
otherConversation.convUsers:
Ron Klein
Rakesh K
*** done output ***
And a final note:
I'd use the generic List instead of the outdated ArrayList, unless you're bound to .NET 1.*.
One good course of action is to expose this object as a DataContract to a framework like WCF, and use the appropriate transports available in that framework.
For example:
[DataContract]
class Conversation
{
private string convName, convOwner;
public ArrayList convUsers;
[DataMember]
public string ConvName
{
get { return this.convName; }
}
[DataMember]
public string ConvOwner
{
get { return this.convOwner; }
}
}
You need to make your object serializable.
I have been using BinaryFormatter to serialise data to disk but it doesn't seem very scalable. I've created a 200Mb data file but am unable to read it back in (End of Stream encountered before parsing was completed). It tries for about 30 minutes to deserialise and then gives up. This is on a fairly decent quad-cpu box with 8Gb RAM.
I'm serialising a fairly large complicated structure.
htCacheItems is a Hashtable of CacheItems. Each CacheItem has several simple members (strings + ints etc) and also contains a Hashtable and a custom implementation of a linked list. The sub-hashtable points to CacheItemValue structures which is currently a simple DTO which contains a key and a value. The linked list items are also equally simple.
The data file that fails contains about 400,000 CacheItemValues.
Smaller datasets work well (though takes longer than i'd expect to deserialize and use a hell of a lot of memory).
public virtual bool Save(String sBinaryFile)
{
bool bSuccess = false;
FileStream fs = new FileStream(sBinaryFile, FileMode.Create);
try
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(fs, htCacheItems);
bSuccess = true;
}
catch (Exception e)
{
bSuccess = false;
}
finally
{
fs.Close();
}
return bSuccess;
}
public virtual bool Load(String sBinaryFile)
{
bool bSuccess = false;
FileStream fs = null;
GZipStream gzfs = null;
try
{
fs = new FileStream(sBinaryFile, FileMode.OpenOrCreate);
if (sBinaryFile.EndsWith("gz"))
{
gzfs = new GZipStream(fs, CompressionMode.Decompress);
}
//add the event handler
ResolveEventHandler resolveEventHandler = new ResolveEventHandler(AssemblyResolveEventHandler);
AppDomain.CurrentDomain.AssemblyResolve += resolveEventHandler;
BinaryFormatter formatter = new BinaryFormatter();
htCacheItems = (Hashtable)formatter.Deserialize(gzfs != null ? (Stream)gzfs : (Stream)fs);
//remove the event handler
AppDomain.CurrentDomain.AssemblyResolve -= resolveEventHandler;
bSuccess = true;
}
catch (Exception e)
{
Logger.Write(new ExceptionLogEntry("Failed to populate cache from file " + sBinaryFile + ". Message is " + e.Message));
bSuccess = false;
}
finally
{
if (fs != null)
{
fs.Close();
}
if (gzfs != null)
{
gzfs.Close();
}
}
return bSuccess;
}
The resolveEventHandler is just a work around because i'm serialising the data in one application and loading it in another (http://social.msdn.microsoft.com/Forums/en-US/netfxbcl/thread/e5f0c371-b900-41d8-9a5b-1052739f2521)
The question is, how can I improve this? Is data serialisation always going to be inefficient, am i better off writing my own routines?
I would personally try to avoid the need for the assembly-resolve; that has a certain smell about it. If you must use BinaryFormatter, then I'd simply put the DTOs into a separate library (dll) that can be used in both applications.
If you don't want to share the dll, then IMO you shouldn't be using BinaryFormatter - you should be using a contract-based serializer, such as XmlSerializer or DataContractSerializer, or one of the "protocol buffers" implementations (and to repeat Jon's disclaimer: I wrote one of the others).
200MB does seem pretty big, but I wouldn't have expected it to fail. One possible cause here is the object tracking it does for the references; but even then, this surprises me.
I'd love to see a simplified object model to see if it is a "fit" for any of the above.
Here's an example that attempts to mirror your setup from the description using protobuf-net. Oddly enough there seems to be a glitch working with the linked-list, which I'll investigate; but the rest seems to work:
using System;
using System.Collections.Generic;
using System.IO;
using ProtoBuf;
[ProtoContract]
class CacheItem
{
[ProtoMember(1)]
public int Id { get; set; }
[ProtoMember(2)]
public int AnotherNumber { get; set; }
private readonly Dictionary<string, CacheItemValue> data
= new Dictionary<string,CacheItemValue>();
[ProtoMember(3)]
public Dictionary<string, CacheItemValue> Data { get { return data; } }
//[ProtoMember(4)] // commented out while I investigate...
public ListNode Nodes { get; set; }
}
[ProtoContract]
class ListNode // I'd probably expose this as a simple list, though
{
[ProtoMember(1)]
public double Head { get; set; }
[ProtoMember(2)]
public ListNode Tail { get; set; }
}
[ProtoContract]
class CacheItemValue
{
[ProtoMember(1)]
public string Key { get; set; }
[ProtoMember(2)]
public float Value { get; set; }
}
static class Program
{
static void Main()
{
// invent 400k CacheItemValue records
Dictionary<string, CacheItem> htCacheItems = new Dictionary<string, CacheItem>();
Random rand = new Random(123456);
for (int i = 0; i < 400; i++)
{
string key;
CacheItem ci = new CacheItem {
Id = rand.Next(10000),
AnotherNumber = rand.Next(10000)
};
while (htCacheItems.ContainsKey(key = rand.NextString())) {}
htCacheItems.Add(key, ci);
for (int j = 0; j < 1000; j++)
{
while (ci.Data.ContainsKey(key = rand.NextString())) { }
ci.Data.Add(key,
new CacheItemValue {
Key = key,
Value = (float)rand.NextDouble()
});
int tail = rand.Next(1, 50);
ListNode node = null;
while (tail-- > 0)
{
node = new ListNode
{
Tail = node,
Head = rand.NextDouble()
};
}
ci.Nodes = node;
}
}
Console.WriteLine(GetChecksum(htCacheItems));
using (Stream outfile = File.Create("raw.bin"))
{
Serializer.Serialize(outfile, htCacheItems);
}
htCacheItems = null;
using (Stream inFile = File.OpenRead("raw.bin"))
{
htCacheItems = Serializer.Deserialize<Dictionary<string, CacheItem>>(inFile);
}
Console.WriteLine(GetChecksum(htCacheItems));
}
static int GetChecksum(Dictionary<string, CacheItem> data)
{
int chk = data.Count;
foreach (var item in data)
{
chk += item.Key.GetHashCode()
+ item.Value.AnotherNumber + item.Value.Id;
foreach (var subItem in item.Value.Data.Values)
{
chk += subItem.Key.GetHashCode()
+ subItem.Value.GetHashCode();
}
}
return chk;
}
static string NextString(this Random random)
{
const string alphabet = "abcdefghijklmnopqrstuvwxyz0123456789 ";
int len = random.Next(4, 10);
char[] buffer = new char[len];
for (int i = 0; i < len; i++)
{
buffer[i] = alphabet[random.Next(0, alphabet.Length)];
}
return new string(buffer);
}
}
Serialization is tricky, particularly when you want to have some degree of flexibility when it comes to versioning.
Usually there's a trade-off between portability and flexibility of what you can serialize. For example, you might want to use Protocol Buffers (disclaimer: I wrote one of the C# ports) as a pretty efficient solution with good portability and versioning - but then you'll need to translate whatever your natural data structure is into something supported by Protocol Buffers.
Having said that, I'm surprised that binary serialization is failing here - at least in that particular way. Can you get it to fail with a large file with a very, very simple piece of serialization code? (No resolution handlers, no compression etc.)
Something that could help is cascade serializing.
You call mainHashtable.serialize(), which return a XML string for example. This method call everyItemInYourHashtable.serialize(), and so on.
You do the same with a static method in every class, called 'unserialize(String xml)', which unserialize your objetcs and return an object, or a list of objects.
You get the point ?
Of course, you need to implement this method in every of your class you want to be serializable.
Take a look at ISerializable interface, which represent exaclty what I'm describing. IMO, this interface looks too "Microsoft" (no use of DOM, etc), so i created mine, but principle is the same : cascade.