WPF C# - 使用带有代码隐藏属性(变量)的动态设备保存 Canvas

WPF C# - Saving a Canvas with Dynamic Devices with Code Behind properties (variables)

我们已经在整个堆栈溢出和类似站点中搜索了适用于我们的应用程序的内容,但一切都只让我们走到了一半。

我们有一个应用程序允许用户将设备拖放到 drop canvas 上。设备被丢弃后,它们的 "router properties" 被创建,您可以更改它们的名称、地址、添加注释。

我们还让用户在设备之间连接线路。 (我们还将创建的路由器属性添加到可观察集合中)。

我们已经尝试了 xmlserialization,它让我们保存了设备的物理面,但是在加载 xml 文件后,它不再有任何附加的地址、注释等已保存的设备,并且不允许添加连接或转到其属性。

我意识到我们需要以某种方式序列化后面的代码,然后在反序列化时将其添加回每个设备,但我们似乎无法找到一种方法来序列化路由器属性的可观察集合。

有没有人对允许我们保存 canvas、子项及其代码隐藏属性的最简单方法有任何建议?我附上图片供参考,路由器属性 class,如果需要,我很乐意包含任何代码。我们非常感谢任何帮助。

热烈的问候, 泰勒

例如

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
    ...

主窗口Class

 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
                }
            }
        }

不幸的是,我没有看到您当前方法的解决方案,或者至少 none 已经想到了。

这是问题的基本原理

Serialization Limitations of XamlWriter.Save

运行-时间,而不是设计时表示

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.

扩展引用被取消引用

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.

我的第一个解决方案是为每个控件和路由器分配一个 GUID 或 ID 属性。然而,这似乎行不通,XamlWriter.Save 只是不保留绑定或那种性质的东西。

但是我认为你需要从ViewModel第一种方法

来解决这个问题

也就是说,您的 ViewModel 需要保留视觉对象的所有实现属性、位置以及重建 canvas 视觉所需的任何内容。当您创建每个可视路由器时,您需要将其所有相关状态保存在某处

即使实现细节与 Router ViewModel 是分开的,您也可以将它们都序列化并使用某种 ID 在运行时重新链接它们。

虽然我的 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
}

如果您将路由器属性保存到数据库中,路由器实体实际上并不关心 canvas 的视觉布局是什么,它不是真正应该保存的东西,但也许可以保存在一个相关的 table 中,它有一个到所用路由器的映射和一个到它的布局的映射,即 RouterMap Table,带有 RouterProperties 和视觉布局配置的外键

另一种方法是仅从 routerProperties 生成视觉状态并自动生成布局,这很简洁,但您需要实现更多逻辑来自动配置加载时的布局方式。

然而,如果这是一个相当简单的事情,只需使用类似上述的方法将其全部序列化到一个文件中并完成它

希望对您有所帮助