We've searched all over stack overflow and similar sites for something that will work for our app, but everything gets us only halfway there.
We have an application that allows the user to drag and drop devices onto a drop canvas. Upon the device being dropped, their "router properties" are created, and you can change their name, address, add notes.
We also let the user connect lines between the devices. (We also add the router properties that are created to an observable collection).
We have tried xmlserialization
, and it let us save the physical side of the device, but upon loading the xml file, it no longer has the address, notes, etc attached to any saved device, and doesn't allow for adding connections or going to its properties.
I realize that we need to somehow serialize the code behind, then add it back in to each device upon de-serializing, but we can't seem to find a way to serialize the observable collection of router properties.
Does anyone have any suggestions on the simplest way to allow us to save the canvas, children, and their code behind properties? I am attaching pictures for reference, the router properties class and I'm happy to include any code if needed. We really appreciate any help at all.
Warm Regards, Tyler
For example
Class
public class RouterProperties : INotifyPropertyChanged
{
private ArrayList incomingConnections = new ArrayList();
private ArrayList outgoingCnnections = new ArrayList();
private bool isLocked = true;
private bool isSelected = false;
private string deviceName = "Router";
private string hostName = "Host name";
private string routerIP = "192.168.0.1";
private string note = "Notes";
private string status = "Yellow";
private BitmapImage icon;
// getters and setters removed for brevity
public ArrayList IncomingConnections
...
public ArrayList OutgoingCnnections
...
public bool IsLocked
...
public bool IsSelected
...
public string DeviceName
...
public string HostName
...
public string RouterIP
...
public string Note
...
public string Status
...
public BitmapImage Icon
...
MainWindow Class
public ObservableCollection<RouterProperties> devices = new ObservableCollection<RouterProperties>();
EDIT Code to save xaml
// De-Serialize XML to UIElement using a given filename.
public static UIElement DeSerializeXAML(string filename)
{
// Load XAML from file. Use 'using' so objects are disposed of properly.
using (System.IO.FileStream fs = System.IO.File.Open(filename, System.IO.FileMode.Open, System.IO.FileAccess.Read))
{
return System.Windows.Markup.XamlReader.Load(fs) as UIElement;
}
}
// Serializes any UIElement object to XAML using a given filename.
public static void SerializeToXAML(UIElement element, string filename)
{
// Use XamlWriter object to serialize element
string strXAML = System.Windows.Markup.XamlWriter.Save(element);
// Write XAML to file. Use 'using' so objects are disposed of properly.
using (System.IO.FileStream fs = System.IO.File.Create(filename))
{
using (System.IO.StreamWriter streamwriter = new System.IO.StreamWriter(fs))
{
streamwriter.Write(strXAML);
}
}
}
private void menuSave_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.SaveFileDialog dlg = new Microsoft.Win32.SaveFileDialog();
dlg.FileName = "UIElement File"; // Default file name
dlg.DefaultExt = ".xaml"; // Default file extension
dlg.Filter = "Xaml File (.xaml)|*.xaml"; // Filter files by extension
// Show save file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process save file dialog box results
if (result == true)
{
// Save document
string filename = dlg.FileName;
SerializeToXAML(canvasMain, filename);
}
}
private void menuLoad_Click(object sender, RoutedEventArgs e)
{
Microsoft.Win32.OpenFileDialog dlg = new Microsoft.Win32.OpenFileDialog();
dlg.DefaultExt = ".xaml"; // Default file extension
dlg.Filter = "Xaml File (.xaml)|*.xaml"; // Filter files by extension
// Show open file dialog box
Nullable<bool> result = dlg.ShowDialog();
// Process open file dialog box results
if (result == true)
{
string filename = dlg.FileName;
Canvas canvas = DeSerializeXAML(filename) as Canvas;
// Add all child elements (lines, rectangles etc) to canvas
while (canvas.Children.Count > 0)
{
UIElement obj = canvas.Children[0]; // Get next child
canvas.Children.Remove(obj); // Have to disconnect it from result before we can add it
canvasMain.Children.Add(obj); // Add to canvas
}
}
}
Unfortunately i dont see a solve for your current approach, or at least none that has come to mind.
Here are the fundamentals of the problem
Serialization Limitations of XamlWriter.Save
Run-Time, Not Design-Time Representation
The basic philosophy of what is serialized by a call to Save is that the result will be a representation of the object being serialized, at run-time. Many design-time properties of the original XAML file may already be optimized or lost by the time that the XAML is loaded as in-memory objects, and are not preserved when you call Save to serialize. The serialized result is an effective representation of the constructed logical tree of the application, but not necessarily of the original XAML that produced it. These issues make it extremely difficult to use the Save serialization as part of an extensive XAML design surface.
Extension References are Dereferenced
Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process. These were already dereferenced at the time that in-memory objects were created by the application runtime, and the Save logic does not revisit the original XAML to restore such references to the serialized output. This potentially freezes any databound or resource obtained value to be the value last used by the run-time representation, with only limited or indirect ability to distinguish such a value from any other value set locally. Images are also serialized as object references to images as they exist in the project, rather than as original source references, losing whatever filename or URI was originally referenced. Even resources declared within the same page are seen serialized into the point where they were referenced, rather than being preserved as a key of a resource collection.
My first solution would have been to assign a GUID or id to each control and router property. however seemingly this wont work, XamlWriter.Save
just doesn't preserve bindings or things of that nature.
However i think you need to attack this from a ViewModel
first approach
That's to say, that your ViewModel
needs to keep all the implementation properties of your visual objects, the locations and anything needed to rebuild your canvas visually. As you create each visual router you need to keep all of its relevant state somewhere
Even if the implementation details are separate from the the Router ViewModel you could serialize them both and have some sort of ID to relink them at runtime.
Though my Spidey senses tells me you should redesign the architecture a bit to put all the relevant in a single Higher-Level ViewModel
, though this really all depends on what the architecture of the application is.
Maybe you could have a structure like this
[Serializable]
public class RouterAndState
{
public RouterProperties {get;set;}
Public RouterVisualState {get;set;}
}
[Serializable]
public class RouterVisualState
{
// its location (x,y) and anything else it needs to be recreated
}
If you are saving the router properties to a db the router entity really doesn't care what the visual layout of the canvas is, and its not something that really should be saved but maybe it can be saved in a related table that has a map to the routers used and a map to its layout, Ie RouterMap
Table, with foreign keys to the RouterProperties
and Visual Layout Configuration
The other way is to just generate the visual state from the routerProperties and auto generate the layout, this is neat but you will need implement a lot more logic to auto configure how its laid-out when loading .
However if this is a fairly simple things, just serialize it all to a file using something like the above and be done with it
I hope this helps