【问题标题】:WPF C# - Saving a Canvas with Dynamic Devices with Code Behind properties (variables)WPF C# - 使用带有代码隐藏属性(变量)的动态设备保存画布
【发布时间】:2018-01-30 02:24:54
【问题描述】:

我们已经在整个堆栈溢出和类似网站上搜索了适用于我们的应用程序的东西,但一切都让我们只成功了一半。

我们有一个应用程序允许用户将设备拖放到放置画布上。放下设备后,会创建它们的“路由器属性”,您可以更改它们的名称、地址、添加注释。

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

我们尝试过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
    ...

主窗口类

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

【问题讨论】:

  • 当你说序列化后面的代码时,你到底是什么意思?那里保留了哪些属性和状态?
  • 每个设备“属性”只是c#中存在的变量,而不是xaml中的变量,直到它们被调用到第二张图片的属性窗口中的文本框中。序列化仅保存物理 xaml 属性,因此当我们加载保存的 xml 文件时,设备存在于画布上的确切位置,但没有任何已保存的 c# 变量已附加到它们。设备名称、主机名、注释等属性。路由器属性中的字符串。
  • 你能显示保存xmal的代码吗
  • 感谢您查看此内容。添加了代码以在主帖子中的编辑下保存 xaml

标签: c# wpf serialization dynamicobject


【解决方案1】:

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

这是问题的基本原理

Serialization Limitations of XamlWriter.Save

运行时,而非设计时表示

调用 Save 序列化内容的基本原理是 结果将是被序列化的对象的表示,在 运行。原始 XAML 文件的许多设计时属性可能 在加载 XAML 时已经优化或丢失 内存中的对象,并且在调用 Save 时不会保留 连载。序列化的结果是 构建应用程序的逻辑树,但不一定是 生成它的原始 XAML。这些问题使它非常 难以将 Save 序列化用作扩展 XAML 的一部分 设计表面。

扩展引用被取消引用

对各种标记扩展格式的对象的通用引用, 例如 StaticResource 或 Binding,将由 序列化过程。这些在当时已经被取消引用 内存中的对象是由应用程序运行时创建的,并且 保存逻辑不会重新访问原始 XAML 来恢复此类 对序列化输出的引用。这可能会冻结任何 数据绑定或资源获得的值是最后一次使用的值 运行时表示,只有有限或间接的能力 将此类值与本地设置的任何其他值区分开来。图像是 也序列化为对图像的对象引用,因为它们存在于 项目,而不是作为原始源引用,丢失任何东西 最初引用了文件名或 URI。甚至声明了资源 在同一页面内可以看到序列化到它们所在的位置 被引用,而不是被保留为资源的键 收藏。

我的第一个解决方案是为每个控件和路由器属性分配一个 GUID 或 id。但是看起来这不起作用,XamlWriter.Save 只是不保留绑定或这种性质的东西。

但是我认为你需要从ViewModel 第一种方法来攻击它

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

即使实现细节与路由器 ViewModel 分开,您也可以对它们进行序列化,并拥有某种 ID 以在运行时重新链接它们。

尽管我的蜘蛛侠直觉告诉我,您应该重新设计架构,将所有相关内容放在一个更高级别的 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 生成视觉状态并自动生成布局,这很简洁,但您需要实现更多逻辑来自动配置加载时的布局方式。

但是,如果这是一件相当简单的事情,只需使用上述方法将其全部序列化到一个文件中即可完成

希望对你有帮助

【讨论】:

  • 萨鲁曼,非常感谢您的详细回复。我会把这个展示给我的合作伙伴,然后我们会尝试重新设计它的处理方式!
  • 我们最终抓取了序列化,将画布位置添加到路由器属性,并将所有“路由器属性”字符串保存到文本文档中。然后在加载时,遍历每一行并使用文本文档中的属性创建一个设备。
  • @Vertigo 是的,我想你最终可能不得不做这样的事情。但是您可以序列化然后输出并且不需要文本文件。但对你有用的才是最重要的。我认为另一种方法行不通,即序列化 xaml
猜你喜欢
  • 1970-01-01
  • 2021-12-18
  • 1970-01-01
  • 2013-10-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多