我正在开发一款Windows手机应用。我想将一个画布的孩子复制到其他画布上。我可以使用以下代码完成它,但问题是我必须先从一个画布中删除它。代码是:
private void add_template_Click(object sender, RoutedEventArgs e)
{
var childrenList = Template_canvas1.Children.Cast<UIElement>().ToArray();
root.Children.Clear();
foreach (var c in childrenList)
{
Template_canvas1.Children.Remove(c);
root.Children.Add(c);
}
}
我想在画布上保留这些元素。还有另一种方式吗?
答案 0 :(得分:3)
不要尝试将相同的Template_canvas1.Children
添加到root
画布,而是首先复制那些Children
,然后将副本添加到root
画布。< / p>
public static T CloneXaml<T>(T source)
{
string xaml = XamlWriter.Save(source);
StringReader sr = new StringReader(xaml);
XmlReader xr = XmlReader.Create(sr);
return (T)XamlReader.Load(xr);
}
然后将你的循环改为:
foreach (var c in childrenList)
{
var copy = CloneXaml(c);
root.Children.Add(copy);
}
我还没有测试过这段代码,所以你可能需要对它进行一些修改,但它应该让你朝着正确的方向前进。
或者,您可以使用以下从Dr Herbie's answer复制的代码:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.UI.Xaml;
using System.Reflection;
using Windows.UI.Xaml.Controls;
namespace UIElementClone {
public static class UIElementExtensions {
public static T DeepClone<T>(this T source) where T : UIElement {
T result; // Get the type
Type type = source.GetType(); // Create an instance
result = Activator.CreateInstance(type) as T;
CopyProperties<T>(source, result, type);
DeepCopyChildren<T>(source, result);
return result;
}
private static void DeepCopyChildren<T>(T source, T result) where T : UIElement {
// Deep copy children.
Panel sourcePanel = source as Panel;
if (sourcePanel != null) {
Panel resultPanel = result as Panel;
if (resultPanel != null) {
foreach (UIElement child in sourcePanel.Children) {
// RECURSION!
UIElement childClone = DeepClone(child);
resultPanel.Children.Add(childClone);
}
}
}
}
private static void CopyProperties<T>(T source, T result, Type type) where T : UIElement {
// Copy all properties.
IEnumerable<PropertyInfo> properties = type.GetRuntimeProperties();
foreach (var property in properties) {
if (property.Name != "Name") { // do not copy names or we cannot add the clone to the same parent as the original.
if ((property.CanWrite) && (property.CanRead)) {
object sourceProperty = property.GetValue(source);
UIElement element = sourceProperty as UIElement;
if (element != null) {
UIElement propertyClone = element.DeepClone();
property.SetValue(result, propertyClone);
}
else {
try {
property.SetValue(result, sourceProperty);
}
catch (Exception ex) {
System.Diagnostics.Debug.WriteLine(ex);
}
}
}
}
}
}
}
}
如果这些都不适合你,我担心你必须实现自己的序列化程序。看起来David Poll实现了一个体面的serlizer,所以看看。使用他的serlizer就像使用XamlWriter
一样简单,然后你可以使用XamlReader
:
public static T CloneXaml<T>(T source)
{
UiXamlSerializer uxs = new UiXamlSerializer();
string xaml = uxs.Serialize(source);
StringReader sr = new StringReader(xaml);
XmlReader xr = XmlReader.Create(sr);
return (T)XamlReader.Load(xr);
}
要获得此功能,请下载他的Slab library,转到“Binaries”文件夹,并将所有以“SLaB.Utilities.Xaml.Serializer”开头的dll复制到您的项目中。可能还有一些其他dll需要作为依赖。如果你想看一下学习代码,他在库中有示例解决方案。
答案 1 :(得分:1)
如果没有一个好的Minimal, Complete, and Verifiable example能够清楚地显示你所尝试的内容,并准确描述了你想要达到的目标,那么就不可能确切知道最佳答案是什么。
那就是说,鉴于WPF已经知道如何在某种意义上“克隆”元素,通过使用数据模板,你的问题对我来说听起来很像XY Problem。也就是说,你只需想想你需要逐字地克隆已经在可视化树中的元素,而实际上 应该做的是定义一个代表数据的视图模型要显示要“克隆”的元素,定义单个数据模板,使用XAML描述将在视图模型中显示数据的可视元素,然后根据需要随时应用模板视觉元素被“克隆”。
即。它们不会被真正克隆。相反,WPF将自动完全按照您的预期填充视觉元素的全新子树。由于模板允许您完全定义所有方面,因此没有与例如试图将事件订阅连接起来,正确设置绑定等等。
在您的具体示例中(尽管它很模糊),听起来您很可能想要使用ItemsControl
元素,其中ItemsPanel
是Canvas
对象。然后,您可以在DataTemplate
中定义代表单个项的ItemsPanel
;此模板将通过设置其DataType
属性隐式引用,或通过设置ItemsControl.ItemTemplate
属性显式引用。然后,您只需创建ItemsControl
,而不是克隆任何内容,只需要创建数据的视觉副本。
答案 2 :(得分:0)
可以下载完整的最终Windows Phone应用程序here。
存在一些API差异,例如我们必须使用pinfo.SetMethod
来代替pinfo.GetSetMethod()
属性。
其次,我在不知情的情况下没有检查Name
属性,不能复制,否则我们会创建另一个同名的实例。
第三,我发布了简单控件的简单案例,例如Button
,TextBox
,Rectangle
等不包含子项的控件。如果是这种情况,你必须进行递归深度克隆来克隆孩子。因为孩子可以有更多的孩子等等。
foreach (UIElement oldElem in Canvas1.Children)
{
try
{
Type t = oldElem.GetType();
UIElement newElem = (UIElement)Activator.CreateInstance(t);
PropertyInfo[] info = t.GetProperties();
int i = 0;
foreach (PropertyInfo pinfo in info)
{
if (pinfo.Name == "Name") continue;
try
{
if (pinfo.GetSetMethod() != null) // avoid read-only properties
pinfo.SetValue(newElem, pinfo.GetValue(oldElem, null),null);
}
catch (Exception ex)
{
Debug.WriteLine((++i).ToString() + " : " + pinfo.ToString());
}
}
Canvas.SetLeft(newElem, Canvas.GetLeft((oldElem)));
Canvas.SetTop(newElem, Canvas.GetTop((oldElem)));
Canvas2.Children.Add(newElem);
}
catch (Exception ex)
{
}
}
如果您要进行真正的深度克隆,那么使用更简单的方法替换上面的外部try块中的代码:
foreach (UIElement oldElem in Canvas1.Children)
{
try
{
UIElement newElem = oldElem.DeepClone();
Canvas2.Children.Add(newElem);
Canvas.SetLeft(newElem, Canvas.GetLeft(oldElem));
Canvas.SetTop(newElem, Canvas.GetTop(oldElem));
}
catch (Exception ex){ }
}
不了解Windows手机,但在WPF中,这会创建一个新元素并将其放在另一个画布中完全相同的位置。检查它是否符合您的需求,否则我会再次更新。
foreach (UIElement oldElem in Canvas1.Children)
{
Type t = oldElem.GetType();
UIElement newElem = (UIElement)Activator.CreateInstance(t);
PropertyInfo[] info = t.GetProperties();
int i = 0;
foreach (PropertyInfo pinfo in info)
{
try
{
if (pinfo.SetMethod != null) // avoid read-only properties
pinfo.SetValue(newElem, pinfo.GetValue(oldElem));
}
catch (Exception ex)
{
Debug.WriteLine((++i).ToString() + " : " + pinfo.ToString());
}
}
Canvas.SetLeft(newElem, Canvas.GetLeft((oldElem)));
Canvas.SetTop(newElem, Canvas.GetTop((oldElem)));
Canvas.SetRight(newElem, Canvas.GetRight((oldElem)));
Canvas.SetBottom(newElem, Canvas.GetBottom((oldElem)));
Canvas2.Children.Add(newElem);
}