如何克隆一个WPF对象?

42
4个回答

65

我最简单的做法是使用XamlWriter将WPF对象保存为字符串。Save方法将序列化对象及其逻辑树中所有子元素。现在,您可以创建一个新对象并使用XamlReader加载它。

例如:将对象写入xaml(假设该对象是Grid控件):

string gridXaml = XamlWriter.Save(myGrid);

将其加载到一个新对象中:

StringReader stringReader = new StringReader(gridXaml);
XmlReader xmlReader = XmlReader.Create(stringReader);
Grid newGrid = (Grid)XamlReader.Load(xmlReader);

1
请记住,它也会克隆名称,如果它们要放置在同一根容器中作为 UI 元素使用,这将使其使用变得更加复杂。 - toad
这个好像不能保留动画效果,对吧? - Erich Mirabal
8
要明确的是,这只是解决方案的一半(就像在08年那样)。这将导致绑定被评估并且结果被序列化。如果您希望保留绑定(如问题所问),则必须在运行时向Binding类型添加ExpressionConverter(请参见我的问题的第二部分以获取相关链接),或者查看下面的答案以了解如何在4.0中执行此操作。 - user1228
太棒了,完美无缺 :) - Ahmed Mahmoud
很遗憾XamlWriter速度太慢了。希望有一个替代方案。 - willem
无法使用绑定泛型类型的控件。 - Henka Programmer

37
在.NET 4.0中,新的XAML序列化堆栈使这个过程变得非常容易。
var sb = new StringBuilder();
var writer = XmlWriter.Create(sb, new XmlWriterSettings
{
    Indent = true,
    ConformanceLevel = ConformanceLevel.Fragment,
    OmitXmlDeclaration = true,
    NamespaceHandling = NamespaceHandling.OmitDuplicates, 
});
var mgr = new XamlDesignerSerializationManager(writer);

// HERE BE MAGIC!!!
mgr.XamlWriterMode = XamlWriterMode.Expression;
// THERE WERE MAGIC!!!

System.Windows.Markup.XamlWriter.Save(this, mgr);
return sb.ToString();

我不明白这与仅使用XamlWriter.Save有何不同?当尝试序列化DataGrid时,我没有看到任何不同的结果。 - JP Richardson
6
@JP 抱歉,这并不是非常清楚... 问题是如何克隆同时保留绑定。 标记的答案是部分正确的; 实际上,如果你这样做,你会发现你的绑定将被评估,结果(而不是绑定本身)将被序列化。在我的问题中,我添加了解决方案的第二部分,即添加ExpressionConverter并在运行时将其添加到Binding类型中。这有点模糊。通过这个答案也可以实现同样的结果——看到这句话“HERE BE MAGIC”?那就指示序列化程序不要在序列化时评估绑定。很棒。 - user1228
是的,我看到了。我仍在解决我的Datagrid问题,没有注意到数据绑定方面有任何不同的结果。我的Datagrid仍然是空白的。我一定是漏掉了其他的东西。无论如何,我会给你点赞,因为你指出了这一切。 - JP Richardson
@JPRichardson:将其序列化为文本并检查结果,以查看是否保留了“{Binding}”。 - user1228
1
我觉得它很有用,我正在编写一个动态数据输入表单,这解决了我遇到的绑定问题。谢谢。 - Masoud

5
这里有一些很好的答案,非常有用。我尝试过各种方法来复制绑定信息,包括http://pjlcon.wordpress.com/2011/01/14/change-a-wpf-binding-from-sync-to-async-programatically/中概述的方法,但这里的信息是互联网上最好的!
我创建了一个可重用的扩展方法来处理InvalidOperationException“Binding cannot be changed after it has been used.”在我的场景中,我正在维护别人编写的代码,在DevExpress DXGrid框架的重大升级后,它不再起作用。以下完美地解决了我的问题。我返回对象的部分可能更好,并且稍后将对其进行重构。
/// <summary>
/// Extension methods for the WPF Binding class.
/// </summary>
public static class BindingExtensions
{
    public static BindingBase CloneViaXamlSerialization(this BindingBase binding)
    {
        var sb = new StringBuilder();
        var writer = XmlWriter.Create(sb, new XmlWriterSettings
        {
            Indent = true,
            ConformanceLevel = ConformanceLevel.Fragment,
            OmitXmlDeclaration = true,
            NamespaceHandling = NamespaceHandling.OmitDuplicates,
        });
        var mgr = new XamlDesignerSerializationManager(writer);

        // HERE BE MAGIC!!!
        mgr.XamlWriterMode = XamlWriterMode.Expression;
        // THERE WERE MAGIC!!!

        System.Windows.Markup.XamlWriter.Save(binding, mgr);
        StringReader stringReader = new StringReader(sb.ToString());
        XmlReader xmlReader = XmlReader.Create(stringReader);
        object newBinding = (object)XamlReader.Load(xmlReader);
        if (newBinding == null)
        {
            throw new ArgumentNullException("Binding could not be cloned via Xaml Serialization Stack.");
        }

        if (newBinding is Binding)
        {
            return (Binding)newBinding;
        }
        else if (newBinding is MultiBinding)
        {
            return (MultiBinding)newBinding;
        }
        else if (newBinding is PriorityBinding)
        {
            return (PriorityBinding)newBinding;
        }
        else
        {
            throw new InvalidOperationException("Binding could not be cast.");
        }
    }
}

0

这样怎么样:

    public static T DeepClone<T>(T from)
    {
        using (MemoryStream s = new MemoryStream())
        {
            BinaryFormatter f = new BinaryFormatter();
            f.Serialize(s, from);
            s.Position = 0;
            object clone = f.Deserialize(s);

            return (T)clone;
        }
    }

当然,这个深度克隆任何对象,并不一定是最快的解决方案,但它需要的维护最少... :)


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接