Browsing posts in: Windows Phone 7

Cómo enviar comentarios a WordPress desde Windows Phone [C#]

Por si alguna vez necesitáis enviar comentarios a un blog de WordPress desde vuestra aplicación de Windows Phone 7 (en realidad se puede adaptar el código a otras plataformas con dos pequeños tweaks), lo que necesitáis hacer es lo siguiente:

##El problema, identificando requerimientos##

Necesitamos enviar un comentario a un blog de WordPress, se nos puede ocurrir que tenemos que enviar una petición con unos datos al blog. ¿A qué dirección? ¿Qué datos? ¿En qué forma?

WordPress tiene una página para eso, es http://nombreblog.com/wp-comments-post.php. En este caso, por ejemplo, es http://danielrozo.es/wp-comments-post.php. Ya tenemos resuelta la primera duda.

Ahora necesitamos saber qué datos debemos enviar, para ello sólo basta con mirar el formulario que aparece debajo de cada entrada de WordPress, o bien, mirar el código fuente de wp-comments-post.php y ver las peticiones POST que acepta, que son:

  • comment_post_ID.
  • author.
  • email.
  • comment.
  • url.

Con esto hemos respondido las preguntas que nos quedaban: debemos enviar todos estos datos en una petición de tipo POST a /wp-comments-post.php.

El diseño de una página que haga esto es muy sencillo, os lo dejo a vosotros (son textboxes y fuera).

La única duda que queda por contestar es ¿Cómo identifica wp-comments-post.php el post al que quiero enviar el comentario? Pues con el comment_post_ID que le enviamos. Como consecuencia de ello, necesitamos el ID del post, en los WordPress que no tengan vanity URLs es el número que sale después de ?p= en la dirección. Para los que sí la tengan, es necesario cogerlo del RSS. Por ejemplo, si vemos el RSS de esta página vemos que dentro de cada <item> hay un <guid>, este es el elemento que necesitamos tratar para coger el ID del post. Vamos a ver cómo hacerlo usando LINQ, que es una maravilla. Supondré que tenéis una clase Post con los atributos creados, getters, setters y esas cositas que hacen falta. Este código debéis encapsularlo en un método al que le pasáis la string del XML del RSS que descargáis.


StringReader stringReader = new StringReader(feedXML);
 XmlReader xmlReader = XmlReader.Create(stringReader);
 XDocument loadedPosts = XDocument.Load(xmlReader);
 XNamespace dc = "http://purl.org/dc/elements/1.1/";
 XNamespace content = XNamespace.Get("http://purl.org/rss/1.0/modules/content/");
 var data = from query in loadedPosts.Descendants("item")
 select new Post
  {
   NombreBlog=(string)query.Parent.Element("title"),
   Titulo = (string)query.Element("title"),
   Autor = (string)query.Element(dc + "creator"),
   Contenido = (string)query.Element(content + "encoded"),
   Fecha = (string)query.Element("pubDate"),
   Link = (string)query.Element("link"),
   ID=getId((string)query.Element("guid")),
   Imagen = getImage((string)query.Element(content + "encoded"))
  };
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
  feedListBox.ItemsSource = data;
});

Dejando de lado los detalles del asunto, sólo os hace falta el método getId, que es el siguiente:

</pre>
private string getId(string s)
 {
string [] sa=s.Split('=');
return sa[1];
 }

Separa la string que le pasas, que será de tipo http://nombreblog.com/?p=xxxx, y devuelve solo el “xxxx” que es el ID.

Ahora sólo nos queda montar la petición POST que será enviada, que será algo parecido a esto:


WebClient webClient = new WebClient();
 webClient.Headers[HttpRequestHeader.ContentType] = "application/x-www-form-urlencoded";
 var uri = new Uri("http://" + url + "/wp-comments-post.php?", UriKind.Absolute);
 StringBuilder postData = new StringBuilder();
 postData.AppendFormat("{0}={1}", "comment_post_ID", HttpUtility.UrlEncode(_sItem.ID));
 postData.AppendFormat("&{0}={1}", "author", HttpUtility.UrlEncode(nombreTb.Text));
 postData.AppendFormat("&{0}={1}", "email", HttpUtility.UrlEncode(correoTb.Text));
 postData.AppendFormat("&{0}={1}", "comment", HttpUtility.UrlEncode(comentarioTb.Text));
 postData.AppendFormat("&{0}={1}", "url", HttpUtility.UrlEncode(urlTb.Text));
 MessageBox.Show("" + postData);

webClient.Headers[HttpRequestHeader.ContentLength] = postData.Length.ToString();
 webClient.UploadStringCompleted += new UploadStringCompletedEventHandler(webClient_UploadStringCompleted);
 webClient.UploadProgressChanged += webClient_UploadProgressChanged;
 webClient.UploadStringAsync(uri, "POST", postData.ToString());

Obviamente necesitáis tener los textboxes montados en vuestro XAML e implementar los métodos que se llaman por UploadStringCompleted (que será mostrar un mensaje de “Comentario posteado!” o algo y el UploadProgressChanged, que vendrá a actualizar una barra de progreso que tengáis con el ProgressPercentage del UploadProgressChangedEventArgs que le paséis al método.

¡Esto es todo, saludos!


Cómo evitar acciones de zoom en WebBrowser embebido en WP7

Seguramente muchas veces se os ha presentado la situación en la que tenéis que presentar contenido en HTML en vuestras aplicaciones, y la labor de pasar ese HTML a XAML para presentarlo en una cosa diferente a un WebBrowser embebido es algo inviable, casi. El problema viene cuando queréis tener una experiencia consistente en la aplicación, y el hecho de que WebBrowser no tenga un atributo para prohibir cualquier tipo de zoom o que usar un viewport tenga un comportamiento errático, no ayuda. Pero no os preocupéis, hay solución.

Nota: truco especialmente útil para los que osen a usar PhoneGap.

Como sabréis algunos, un WebBrowser no es un control solamente, sino que es la unión de varios controles. Específicamente:

-WebBrowser
  -Border
    -Border
      -PanZoomContainer
        -Grid
          -Border
            -ContentPresenter
              -TileHost

De hecho es bastante sencillo, un TileHost que es el núcleo de IE9, y el PanZoomContainer, donde se procesan las acciones del touch y clic. PanZoomContainer coge las acciones, las pasa al TileHost y este le da el feedback para convertirlo en gestos de pinch-to-zoom. ¿Qué significa esto? Pues básicamente significa que podemos intereptar esas acciones en el Border que hay en medio del PanZoom y el TileHost antes de que se procesen y hacer que aunque el usuario realice el gesto de pinch-to-zoom, el WebBrowser no se mueva en absoluto (no como con el viewport, que hace un amago de zoom para volver al estado original).

La solución, en código

Ya hemos dado la idea general de la solución, de qué es lo que haremos, ahora vamos al cómo.

Para empezar, abrimos un proyecto de Windows Phone, insertamos un WebBrowser, un TextBox y un botón. Supongamos que el WebBrowser se llama wb, el TextBox tb y el Botón bt. Soy insultantemente original con mis nombres. Añadimos un evento clic para el botón y escribimos dentro:


wb.Navigate(tb.Text);

Si insertamos cualquier web, el usuario sigue pudiendo hacer algo de zoom, incluso con viewport tag.
Vamos al lío, en líneas generales será esto:

  1. Añadimos LinqToVisualTree a nuestro proyecto.
  2. Añadimos WebBrowserHelper al proyecto.
  3. Instanciamos un WebBrowserHelper con el WebBrowser.
  4. ?????.
  5. PROFIT!!!

Primer paso

Añadimos LinqToVisualTree: Proyecto->agregar clase->”LinqToVisualTree” y pegamos esto:

<pre>using System;
using System.Linq;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;

namespace LinqToVisualTree
{
    /// <summary>
    /// Adapts a DependencyObject to provide methods required for generate
    /// a Linq To Tree API
    /// </summary>
    public class VisualTreeAdapter : ILinqTree<DependencyObject>
    {
        private DependencyObject _item;

        public VisualTreeAdapter(DependencyObject item)
        {
            _item = item;
        }

        public IEnumerable<DependencyObject> Children()
        {
            int childrenCount = VisualTreeHelper.GetChildrenCount(_item);
            for (int i = 0; i < childrenCount; i++)
            {
                yield return VisualTreeHelper.GetChild(_item, i);
            }
        }

        public DependencyObject Parent
        {
            get
            {
                return VisualTreeHelper.GetParent(_item);
            }
        }
    }
}

namespace LinqToVisualTree
{
    /// <summary>
    /// Defines an interface that must be implemented to generate the LinqToTree methods
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface ILinqTree<T>
    {
        IEnumerable<T> Children();

        T Parent { get; }
    }

    public static class TreeExtensions
    {
        /// <summary>
        /// Returns a collection of descendant elements.
        /// </summary>
        public static IEnumerable<DependencyObject> Descendants(this DependencyObject item)
        {
            ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);
            foreach (var child in adapter.Children())
            {
                yield return child;

                foreach (var grandChild in child.Descendants())
                {
                    yield return grandChild;
                }
            }
        }

        /// <summary>
        /// Returns a collection containing this element and all descendant elements.
        /// </summary>
        public static IEnumerable<DependencyObject> DescendantsAndSelf(this DependencyObject item)
        {
            yield return item;

            foreach (var child in item.Descendants())
            {
                yield return child;
            }
        }

        /// <summary>
        /// Returns a collection of ancestor elements.
        /// </summary>
        public static IEnumerable<DependencyObject> Ancestors(this DependencyObject item)
        {
            ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);

            var parent = adapter.Parent;
            while (parent != null)
            {
                yield return parent;
                adapter = new VisualTreeAdapter(parent);
                parent = adapter.Parent;
            }
        }

        /// <summary>
        /// Returns a collection containing this element and all ancestor elements.
        /// </summary>
        public static IEnumerable<DependencyObject> AncestorsAndSelf(this DependencyObject item)
        {
            yield return item;

            foreach (var ancestor in item.Ancestors())
            {
                yield return ancestor;
            }
        }

        /// <summary>
        /// Returns a collection of child elements.
        /// </summary>
        public static IEnumerable<DependencyObject> Elements(this DependencyObject item)
        {
            ILinqTree<DependencyObject> adapter = new VisualTreeAdapter(item);
            foreach (var child in adapter.Children())
            {
                yield return child;
            }
        }

        /// <summary>
        /// Returns a collection of the sibling elements before this node, in document order.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsBeforeSelf(this DependencyObject item)
        {
            if (item.Ancestors().FirstOrDefault() == null)
                yield break;
            foreach (var child in item.Ancestors().First().Elements())
            {
                if (child.Equals(item))
                    break;
                yield return child;
            }
        }

        /// <summary>
        /// Returns a collection of the after elements after this node, in document order.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsAfterSelf(this DependencyObject item)
        {
            if (item.Ancestors().FirstOrDefault() == null)
                yield break;
            bool afterSelf = false;
            foreach (var child in item.Ancestors().First().Elements())
            {
                if (afterSelf)
                    yield return child;

                if (child.Equals(item))
                    afterSelf = true;
            }
        }

        /// <summary>
        /// Returns a collection containing this element and all child elements.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsAndSelf(this DependencyObject item)
        {
            yield return item;

            foreach (var child in item.Elements())
            {
                yield return child;
            }
        }

        /// <summary>
        /// Returns a collection of descendant elements which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> Descendants<T>(this DependencyObject item)
        {
            return item.Descendants().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection of the sibling elements before this node, in document order
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsBeforeSelf<T>(this DependencyObject item)
        {
            return item.ElementsBeforeSelf().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection of the after elements after this node, in document order
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsAfterSelf<T>(this DependencyObject item)
        {
            return item.ElementsAfterSelf().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection containing this element and all descendant elements
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this DependencyObject item)
        {
            return item.DescendantsAndSelf().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection of ancestor elements which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> Ancestors<T>(this DependencyObject item)
        {
            return item.Ancestors().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection containing this element and all ancestor elements
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this DependencyObject item)
        {
            return item.AncestorsAndSelf().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection of child elements which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> Elements<T>(this DependencyObject item)
        {
            return item.Elements().Where(i => i is T).Cast<DependencyObject>();
        }

        /// <summary>
        /// Returns a collection containing this element and all child elements.
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this DependencyObject item)
        {
            return item.ElementsAndSelf().Where(i => i is T).Cast<DependencyObject>();
        }

    }

    public static class EnumerableTreeExtensions
    {
        /// <summary>
        /// Applies the given function to each of the items in the supplied
        /// IEnumerable.
        /// </summary>
        private static IEnumerable<DependencyObject> DrillDown(this IEnumerable<DependencyObject> items,
            Func<DependencyObject, IEnumerable<DependencyObject>> function)
        {
            foreach (var item in items)
            {
                foreach (var itemChild in function(item))
                {
                    yield return itemChild;
                }
            }
        }

        /// <summary>
        /// Applies the given function to each of the items in the supplied
        /// IEnumerable, which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> DrillDown<T>(this IEnumerable<DependencyObject> items,
            Func<DependencyObject, IEnumerable<DependencyObject>> function)
            where T : DependencyObject
        {
            foreach (var item in items)
            {
                foreach (var itemChild in function(item))
                {
                    if (itemChild is T)
                    {
                        yield return (T)itemChild;
                    }
                }
            }
        }

        /// <summary>
        /// Returns a collection of descendant elements.
        /// </summary>
        public static IEnumerable<DependencyObject> Descendants(this IEnumerable<DependencyObject> items)
        {
            return items.DrillDown(i => i.Descendants());
        }

        /// <summary>
        /// Returns a collection containing this element and all descendant elements.
        /// </summary>
        public static IEnumerable<DependencyObject> DescendantsAndSelf(this IEnumerable<DependencyObject> items)
        {
            return items.DrillDown(i => i.DescendantsAndSelf());
        }

        /// <summary>
        /// Returns a collection of ancestor elements.
        /// </summary>
        public static IEnumerable<DependencyObject> Ancestors(this IEnumerable<DependencyObject> items)
        {
            return items.DrillDown(i => i.Ancestors());
        }

        /// <summary>
        /// Returns a collection containing this element and all ancestor elements.
        /// </summary>
        public static IEnumerable<DependencyObject> AncestorsAndSelf(this IEnumerable<DependencyObject> items)
        {
            return items.DrillDown(i => i.AncestorsAndSelf());
        }

        /// <summary>
        /// Returns a collection of child elements.
        /// </summary>
        public static IEnumerable<DependencyObject> Elements(this IEnumerable<DependencyObject> items)
        {
            return items.DrillDown(i => i.Elements());
        }

        /// <summary>
        /// Returns a collection containing this element and all child elements.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsAndSelf(this IEnumerable<DependencyObject> items)
        {
            return items.DrillDown(i => i.ElementsAndSelf());
        }

        /// <summary>
        /// Returns a collection of descendant elements which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> Descendants<T>(this IEnumerable<DependencyObject> items)
            where T : DependencyObject
        {
            return items.DrillDown<T>(i => i.Descendants());
        }

        /// <summary>
        /// Returns a collection containing this element and all descendant elements.
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> DescendantsAndSelf<T>(this IEnumerable<DependencyObject> items)
            where T : DependencyObject
        {
            return items.DrillDown<T>(i => i.DescendantsAndSelf());
        }

        /// <summary>
        /// Returns a collection of ancestor elements which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> Ancestors<T>(this IEnumerable<DependencyObject> items)
            where T : DependencyObject
        {
            return items.DrillDown<T>(i => i.Ancestors());
        }

        /// <summary>
        /// Returns a collection containing this element and all ancestor elements.
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> AncestorsAndSelf<T>(this IEnumerable<DependencyObject> items)
            where T : DependencyObject
        {
            return items.DrillDown<T>(i => i.AncestorsAndSelf());
        }

        /// <summary>
        /// Returns a collection of child elements which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> Elements<T>(this IEnumerable<DependencyObject> items)
            where T : DependencyObject
        {
            return items.DrillDown<T>(i => i.Elements());
        }

        /// <summary>
        /// Returns a collection containing this element and all child elements.
        /// which match the given type.
        /// </summary>
        public static IEnumerable<DependencyObject> ElementsAndSelf<T>(this IEnumerable<DependencyObject> items)
            where T : DependencyObject
        {
            return items.DrillDown<T>(i => i.ElementsAndSelf());
        }
    }
}

Ojo, mirad cómo estamos usando un espacio de nombres ¡diferente al de nuestro proyecto!, a partir de ahora tenéis que usar la directiva

using LinqToVisualTree;

Segundo paso

Añadimos WebBrowserHelper, lo mismo de antes y pegamos:

<pre>using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using LinqToVisualTree;
using Microsoft.Phone.Controls;

/// <summary>
/// Suppresses pinch zoom and optionally scrolling of the WebBrowser control
/// </summary>
public class WebBrowserHelper
{
  private WebBrowser _browser;

  /// <summary>
  /// Gets or sets whether to suppress the scrolling of
  /// the WebBrowser control;
  /// </summary>
  public bool ScrollDisabled { get; set; }

  public WebBrowserHelper(WebBrowser browser)
  {
    _browser = browser;
    browser.Loaded += new RoutedEventHandler(browser_Loaded);
  }

  private void browser_Loaded(object sender, RoutedEventArgs e)
  {
    var border = _browser.Descendants<Border>().Last() as Border;

    border.ManipulationDelta += Border_ManipulationDelta;
    border.ManipulationCompleted += Border_ManipulationCompleted;
  }

  private void Border_ManipulationCompleted(object sender,
                                            ManipulationCompletedEventArgs e)
  {
    // suppress zoom
    if (e.FinalVelocities.ExpansionVelocity.X != 0.0 ||
        e.FinalVelocities.ExpansionVelocity.Y != 0.0)
      e.Handled = true;
  }

  private void Border_ManipulationDelta(object sender,
                                        ManipulationDeltaEventArgs e)
  {
    // suppress zoom
    if (e.DeltaManipulation.Scale.X != 0.0 ||
        e.DeltaManipulation.Scale.Y != 0.0)
      e.Handled = true;
  }
}

Tercer paso

Esto es lo más complicado que vais a ver de todo el proceso #ironía.


WebBrowserHelper wbh = new WebBrowserHelper(wb); //wb era nuestro WebBrowser integrado en la app

wb.Navigate(tb.Text);

Y ya está todo hecho! Ahora las aplicaciones que hagáis con WebBrowser (como lectores RSS o PhoneGap) tendrán esa consistencia de uso como si hubiérais pasado todo el HTML a un TextBlock 😉