softklin Posted December 22, 2009 at 11:09 AM Report Share #301799 Posted December 22, 2009 at 11:09 AM Boas pessoal. Estou a fazer umas experiências em C#, usando WPF. Mais concretamente, estou a fazer uma pequena aplicação de desenho de polígonos, e queria implementar um sistema de histórico, para poder anular alterações (undo e redo). Estou a pensar fazer este sistema usando listas, nas quais cada nó tem um estado do canvas que estou a utilizar. O meu problema é que não sei como fazer clone dos objectos canvas (System.Window.Controls.Canvas), visto que não oferece um método clone, copy, wiseClone, ou similar. Estou a tentar copiar os objectos, visto que uma atribuição apenas aponta para o objecto original, correcto? Se ajudar, a minha janela tem um objecto canvas, do tipo que já referi, e mais uns botões. Desde já agradeço a vossa ajuda, Cumps. Nick antigo: softclean | Tens um projeto? | Wiki P@P Ajuda a comunidade! Se encontrares algo de errado, usa a opção "Denunciar" por baixo de cada post. Link to comment Share on other sites More sharing options...
apocsantos Posted December 22, 2009 at 12:11 PM Report Share #301808 Posted December 22, 2009 at 12:11 PM Existem alguns exemplos na net, mas quase todos com silverlight. Talvez sirvam para o que queres, o que me parece mais adequado é o que se encontra no ultimo reply do post que te indico abaixo: http://forums.silverlight.net/forums/t/1730.aspx Parece relativamente simples, mas não tive a oportunidade de o testar. Cumprimentos "A paciência é uma das coisas que se aprendeu na era do 48k" O respeito é como a escrita de código, uma vez perdido, dificilmente se retoma o habito" Link to comment Share on other sites More sharing options...
softklin Posted December 22, 2009 at 01:42 PM Author Report Share #301817 Posted December 22, 2009 at 01:42 PM Não estou a conseguir, ou pelo menos não estou a ter os resultados pretendidos. Optei por usar um código mais em cima, devido ao comentário do autor do último post: I have put together a similar method using Linq just for fun. Thismethod unfortunately does a horrible job of cloning some runtime types such as framework template, control template and so on. I suppose i'llbe using the SilverlightContrib XamlWriter instead. Não percebo bem se será o meu caso, no meu programa tenho um canvas que arrastei da toolbox. Deixo aqui o código que estou a utilizar: /// <summary> /// Cria um ponto de histórico para o canvas actual /// </summary> void createRestorePoint() { Canvas backup = Clone(myCanvas); history.AddLast(backup); } /// <summary> /// Clona objectos /// </summary> /// <typeparam name="T">Tipo de dados genérico</typeparam> /// <param name="source">Dados a copiar</param> /// <returns>Cópia do objecto</returns> /// <see cref="http://forums.silverlight.net/forums/t/1730.aspx"/> private static T Clone<T>(T source) { T cloned = (T)Activator.CreateInstance(source.GetType()); foreach (PropertyInfo curPropInfo in source.GetType().GetProperties()) { if (curPropInfo.GetGetMethod() != null && (curPropInfo.GetSetMethod() != null)) { // Handle Non-indexer properties if (curPropInfo.Name != "Item") { // get property from source object getValue = curPropInfo.GetGetMethod().Invoke(source, new object[] { }); // clone if needed if (getValue != null && getValue is DependencyObject) getValue = Clone((DependencyObject)getValue); // set property on cloned curPropInfo.GetSetMethod().Invoke(cloned, new object[] { getValue }); } // handle indexer else { // get count for indexer int numberofItemInColleciton = (int) curPropInfo.ReflectedType.GetProperty("Count").GetGetMethod().Invoke(source, new object[] { }); // run on indexer for (int i = 0; i < numberofItemInColleciton; i++) { // get item through Indexer object getValue = curPropInfo.GetGetMethod().Invoke(source, new object[] { i }); // clone if needed if (getValue != null && getValue is DependencyObject) getValue = Clone((DependencyObject)getValue); // add item to collection curPropInfo.ReflectedType.GetMethod("Add").Invoke(cloned, new object[] { getValue }); } } } } return cloned; } private void setNewPoint(object sender, MouseButtonEventArgs e) { thePoints.Add(e.GetPosition(myCanvas)); Polygon dot = new Polygon(); dot.Points = createSquareFromCenter(e.GetPosition(myCanvas), 5); dot.Fill = new SolidColorBrush(Color.FromArgb(60, 0, 0, 0)); myCanvas.Children.Add(dot); } private void button1_Click(object sender, RoutedEventArgs e) { //restaura para o primeiro ponto Canvas oldCanvas = Clone(history.First.Value); history.RemoveFirst(); myCanvas = Clone(oldCanvas); myCanvas.UpdateLayout(); } Esta última função button1_Click é um botão de teste que tenho aqui para me restaurar para o primeiro ponto de todos. De seguida elimina-o. Não tem lógica, mas é só mesmo para testar o sistema. Após a execução da função de restaurar, obtenho um canvas igual ao anterior, mas "congelado", porque a função setNewPoint, cujo objectivo é deixar algumas marcas de referências para os pontos dos polígonos, não está a funcionar, não cria pontos nenhuns... Nem uma simples função de apagar tudo funciona... O estranho é que não lança nenhuma excepção. Quando vou à vista de debug, aparentemente está tudo bem, porque a lista de histórico fica com elementos do tipo Canvas. Alguma ideia? 😉 Notas: - mycanvas é o objecto gráfico que arrastei da toolbox. - Aquela função do meio, clone, foi a que copiei do website. Nick antigo: softclean | Tens um projeto? | Wiki P@P Ajuda a comunidade! Se encontrares algo de errado, usa a opção "Denunciar" por baixo de cada post. Link to comment Share on other sites More sharing options...
apocsantos Posted December 22, 2009 at 02:45 PM Report Share #301825 Posted December 22, 2009 at 02:45 PM Vou testar aqui. Estive a ler o codigo todo, e pelo que percebi ele consegue clonar o objecto canvas. Não entendo no entanto porque o destroi de seguida. history.RemoveFirst(); De certeza que alguma coisa me está a escapar mas ainda não consegui ver o quê. Talvez depois de um café se faça "luz" na mioleira, e consiga perceber. Se conseguires depois mostra como conseguis-te. Agora fiquei curioso 😉 Cumprimentos "A paciência é uma das coisas que se aprendeu na era do 48k" O respeito é como a escrita de código, uma vez perdido, dificilmente se retoma o habito" Link to comment Share on other sites More sharing options...
softklin Posted December 22, 2009 at 03:33 PM Author Report Share #301834 Posted December 22, 2009 at 03:33 PM A intenção do history.removeFirst era para remover o item antigo com o qual ia substituir o canvas actual, myCanvas (por consequência vou tirá-lo do histórico). A função que queria seria algo mais deste género: //restaura para o ponto anterior Canvas oldCanvas = Clone(history.Last.Value); history.RemoveLast(); myCanvas = oldCanvas; //Clone(oldCanvas); myCanvas.UpdateLayout(); Os canvas vão sendo guardadsos por ordem cronológica conforme vão sendo alterados e depois quando é feito um pedido para anular alterações, vou buscar o último estado gravado, e elimino-o do histórico. Mas bem, quanto ao problema do clone, mantém-se... Vou pesquisar mais um pouco a ver se encontro alguma coisa, ou outra solução. Nick antigo: softclean | Tens um projeto? | Wiki P@P Ajuda a comunidade! Se encontrares algo de errado, usa a opção "Denunciar" por baixo de cada post. Link to comment Share on other sites More sharing options...
apocsantos Posted December 22, 2009 at 03:44 PM Report Share #301837 Posted December 22, 2009 at 03:44 PM Não sei se ajudará, mas também andei a pesquisar exemplos, a ver se percebia o que se pode estar a passar. No codeproject encontrei este, mas um graphis engine. Pelo que vi tem uma abordagem diferente da que penso que estejas a fazer ao mesmo problema. Talvez ajude um pouco. Pelo que percebi é mais complicado trabalhar apenas com canvas. http://www.codeproject.com/KB/dotnet/another_graphic_engine.aspx "Q: Why is the GraphicDocument a separate class from Canvas? I could add all the items to draw directly to the Canvas. A: Same reason of the first question: the GraphicDocument is the logical items aggregation, it is the document, the canvas is the surface on which the document is drawn. And again, in this way, I could have the same document open on more canvas." Já percebi a questão do history. Foi comfusão minha literalmente. Cumprimentos "A paciência é uma das coisas que se aprendeu na era do 48k" O respeito é como a escrita de código, uma vez perdido, dificilmente se retoma o habito" Link to comment Share on other sites More sharing options...
softklin Posted December 22, 2009 at 11:44 PM Author Report Share #301950 Posted December 22, 2009 at 11:44 PM Apocsantos, obrigado por toda a ajuda. Graças a ti, consegui chegar à solução dos clones, pelo menos como eu que queria. Resolvi alterar o meu projecto, e considerar um canvas criado programaticamente, me vez de arrastado da toolbox. Também mudei a minha função de clone para uma baseada em parsers de XML, que encontrei em: http://stackoverflow.com/questions/32541/how-can-you-clone-a-wpf-object // gravar o canvas actual (myCanvas) para XAML String curCanvasCode = XamlWriter.Save(myCanvas); StringReader stringReader = new StringReader(curCanvasCode); XmlReader xmlReader = XmlReader.Create(stringReader); // ler o canvas do código XAML e passá-lo para objecto Canvas backup = (Canvas)XamlReader.Load(xmlReader); E funciona, com um pequeno senão: animações, handlers (funções associadas a cliques, etc) são perdidas na transição para XAML, porque esse código é escrito à parte em ficheiros C#/VB. Nesse caso basta, ao recuperar a cópia, definir de novo estes atributos. A título de exemplo, a minha função ficou assim: // buscar o canvas anterior ao histórico e obter o código XAML String prevCanvasCode = XamlWriter.Save(history.Last.Value); StringReader stringReader = new StringReader(prevCanvasCode); XmlReader xmlReader = XmlReader.Create(stringReader); Canvas prevCanvas = (Canvas)XamlReader.Load(xmlReader); // remover o canvas actual dos controlos myGrid.Children.Remove(myCanvas); // o canvas actual é agora o anterior (referência) myCanvas = prevCanvas; // redefinir aributos, handlers, etc, perdidos na transição para XAML prevCanvas.MouseLeftButtonDown += new MouseButtonEventHandler(setNewPoint); // colocar o canvas anterior (agora actual) nos controlos da janela myGrid.Children.Add(prevCanvas); Funciona bem no meu caso. Mais uma vez, obrigado pelos links e pela atenção. 😉 Nick antigo: softclean | Tens um projeto? | Wiki P@P Ajuda a comunidade! Se encontrares algo de errado, usa a opção "Denunciar" por baixo de cada post. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now