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 и автоматически сгенерировать макет, это аккуратно, но вам нужно будет реализовать гораздо больше логики, чтобы автоматически конфигурировать его расположение при загрузке.

Однако, если это довольно простые вещи, просто сериализовать все это в файл, используя что-то вроде выше и покончим с этим

надеюсь, это поможет

Другие вопросы по тегам