Jump to content

[Resolvido] Clonar objectos gráficos


softklin
 Share

Recommended Posts

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

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

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

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

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

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

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

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 account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...

Important Information

By using this site you accept our Terms of Use and Privacy Policy. We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.