WPF C# - Сохранение холста с помощью динамических устройств со свойствами кода за (переменные)
Мы искали все переполнение стека и подобные сайты, чтобы найти что-то, что будет работать для нашего приложения, но все это делает нас только наполовину.
У нас есть приложение, которое позволяет пользователю перетаскивать устройства на холст. После удаления устройства создаются его "свойства маршрутизатора", и вы можете изменить их имя, адрес, добавить заметки.
Мы также позволяем пользователю соединять линии между устройствами. (Мы также добавляем свойства маршрутизатора, которые создаются в наблюдаемую коллекцию).
Мы пробовали xmlserialization
и это позволило нам сохранить физическую часть устройства, но после загрузки xml-файла к нему больше не добавляется адрес, заметки и т. д., привязанные к какому-либо сохраненному устройству, и не разрешается добавлять соединения или переходить к его свойствам.
Я понимаю, что нам нужно каким-то образом сериализовать код, а затем добавить его обратно на каждое устройство после десериализации, но мы не можем найти способ сериализации наблюдаемой коллекции свойств маршрутизатора.
Есть ли у кого-нибудь предложения по простейшему способу сохранения холста, дочерних элементов и их кода в свойствах? Я прилагаю фотографии для справки, класс свойств маршрутизатора, и я рад включить любой код, если это необходимо. Мы очень ценим любую помощь.
С наилучшими пожеланиями, Тайлер
Например
Учебный класс
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
public ObservableCollection<RouterProperties> devices = new ObservableCollection<RouterProperties>();
РЕДАКТИРОВАТЬ код для сохранения 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
}
}
}
1 ответ
К сожалению, я не вижу решения для вашего нынешнего подхода, или, по крайней мере, ни одного, который придет в голову.
Вот основы проблемы
Ограничения сериализации XamlWriter.Save
Время выполнения, а не представление во время разработки
Основная философия сериализации при вызове Save заключается в том, что результатом будет представление сериализуемого объекта во время выполнения. Многие свойства времени разработки исходного файла XAML уже могут быть оптимизированы или потеряны к моменту загрузки XAML как объектов в памяти, и они не сохраняются при вызове Save для сериализации. Сериализованный результат является эффективным представлением построенного логического дерева приложения, но не обязательно исходного XAML, который его создал. Эти проблемы крайне затрудняют использование сериализации Save как части обширной области разработки XAML.
Ссылки на расширение разыменовываются
Распространенные ссылки на объекты, созданные различными форматами расширения разметки, такими как StaticResource или Binding, будут разыменовываться процессом сериализации. Они уже были разыменованы в то время, когда объекты в памяти создавались средой выполнения приложения, и логика Save не восстанавливает исходный XAML для восстановления таких ссылок в сериализованный вывод. Это потенциально замораживает любое полученное значение базы данных или ресурса, чтобы оно стало последним значением, используемым представлением во время выполнения, с ограниченной или косвенной возможностью отличать такое значение от любого другого значения, установленного локально. Изображения также сериализуются как ссылки на объекты к изображениям в том виде, в каком они существуют в проекте, а не как ссылки на оригинальные источники, теряя любое имя файла или URI, на которые изначально ссылались. Даже ресурсы, объявленные на одной и той же странице, считаются сериализованными в точку, на которую они ссылались, а не сохраняются в качестве ключа коллекции ресурсов.
Моим первым решением было бы назначить GUID или идентификатор для каждого элемента управления и свойства маршрутизатора. Однако, казалось бы, это не сработает, XamlWriter.Save
просто не сохраняет привязки или вещи такого рода.
Однако я думаю, что вам нужно атаковать это от ViewModel
первый подход
То есть, ваш ViewModel
необходимо сохранить все свойства реализации ваших визуальных объектов, местоположения и все, что нужно для визуальной перестройки холста. Когда вы создаете каждый визуальный маршрутизатор, вам нужно где-то сохранять все его соответствующие состояния
Даже если подробности реализации отделены от модели маршрутизатора ViewModel, вы можете сериализовать их оба и иметь какой-то идентификатор, чтобы связать их во время выполнения.
Хотя мои чувства Spidey говорят мне, что вы должны немного перестроить архитектуру, чтобы поместить все релевантное в один более высокий уровень ViewModel
хотя все зависит от архитектуры приложения.
Может быть, вы могли бы иметь такую структуру
[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
}
Если вы сохраняете свойства маршрутизатора в БД, сущность маршрутизатора действительно не заботится о том, каков визуальный макет холста, и это не то, что действительно должно быть сохранено, но, возможно, его можно сохранить в связанной таблице с картой к используемым роутерам и карту к его компоновке, т.е. RouterMap
Таблица с внешними ключами к RouterProperties
и визуальная конфигурация макета
Другой способ - просто сгенерировать визуальное состояние из routerProperties и автоматически сгенерировать макет, это аккуратно, но вам нужно будет реализовать гораздо больше логики, чтобы автоматически конфигурировать его расположение при загрузке.
Однако, если это довольно простые вещи, просто сериализовать все это в файл, используя что-то вроде выше и покончим с этим
надеюсь, это поможет