Posts tagged with: C#

UpperCounter Kata con LINQ, primera parte

Hace unos días Roberto Luis publicaba una kata con la que habían estado trabajando en Software Craftmanship Madrid, y me animaba a resolverla. Y no iba yo a negarle tal sugerencia a Roberto, así que aquí está mi intento con LINQ; el vídeo lo dejamos para otra ocasión.

Toda la solución se basa en una sobrecarga del método de extensión Select, que a poca gente he visto usar y que nos permite proyectar, además del elemento sobre el que se está trabajando, el índice del mismo en una lista…¿qué más se puede pedir?

Pues eso, unido a un selector que nos provea sólo los elementos que son mayúscula y ¡magia! Y tanto que magia, porque a ver quién debuggea esto luego si se complica el asunto en un caso de la vida real donde se sustituye un foreach por LINQ. Quedará mucho más h4x0r en código, pero a veces no es necesario, querido R#. En todo caso quería intentarlo, y queda así:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UppercaseSearcher
{
    class UppercaseSearcher
    {
       public static List<int> Search(string input)
        {
            return input.Select((ch, index) => new { index, ch }).Where(x => char.IsUpper(x.ch)).Select(t => t.index).ToList();
        }
    }
}

Paso a paso se ve más sencillo: lo primero que asumimos es tratar la string como un array de chars, de ahí, el primer Select proyecta cada uno de los elementos a un tipo anónimo que no es más que una tupla int,char; seleccionamos sólo los elementos que satisfagan la condición del kata, y aplanamos dicho objeto anónimo para que sólo nos devuelva los índices que los que pasaron la condición.

Sobre los tests, aunque fueron iguales a los de Roberto, y una segunda parte con restricciones hablaremos más adelante, si la solución que se me ocurre no es tan parecida a la de él :).


Generar datos de ejemplo a partir de clases y NBuilder en ASP.NET MVC 4

A estas alturas creo que no es muy necesario introducir lo bueno de los datos de ejemplo en las aplicaciones, sobre todo al momento de probar el diseño de las mismas, o hacer todo tipo de pruebas y perrerías que nos podría meter algún usuario en las vistas, aunque normalmente estas deberían estar controladas aunque sea con un if(), idealmente con anotaciones sobre el modelo. Con NBuilder podemos tener datos de ejemplo para nuestras vistas de ASP.NET MVC 4 de una manera ultra-sencilla, generándolos directamente en el Controller. Voy a suponer que ya habréis creado un nuevo proyecto de MVC 4 e instalado NBuilder desde Nuget. Vaya por delante que esta entrada no pretende ser más que una pequeña introducción a una herramienta que me está resultando útil estos días.

Primero que nada, cabe subrayar cómo NBuilder nos genera los datos de prueba, que dependen del tipo de datos que pretendas generar. Por ejemplo, para las strings será algo como string.Format({0}{1},propertyName, i) dentro de un bucle. Es decir, si tenemos un campo “Name” que es una string, los datos que nos generará serán “Name1, Name2,…,NameN”. Para los números es tan sencillo como un i++ desde 1 a N (1,2,3…N). Para las fechas, parte de la actual y le va sumando un día.

Bien, supongamos que tenemos la siguiente entidad (escribiré el código directamente en WordPress, sabrán perdonarme alguna identación):

public class Event
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Venue { get; set; }
    public string City { get; set; }
    public string Country { get; set; }
}

Entidad modelo sencillísima donde se podrían hacer mil cosas más, así como optimizaciones agrupando en un objeto varias propiedades…pero eso será ejercicio del lector, vamos al grano de lo que queremos con NBuilder. Ahora, en el controlador, tendremos que importar dos namespaces, el de nuestro modelo y Fizzware.NBuilder. Una vez hecho esto podremos escribir un código parecido a este en nuestro controlador:

public ActionResult Index()
{
    var events = Builder.CreateListOfSize(10).Build();
    return View(events);
}

Asumiendo que la vista la habréis creado con clic derecho en la acción -> crear vista tipada, el trabajo está hecho. Pero dejarlo así es un poco soso, teniendo en cuenta las capacidades de creación de contenido casi como queramos que nos provee NBuilder. Por ejemplo, supongamos que queremos hacer que todos los datos sean iguales. No sé bien por qué, pero supongamoslo:

public ActionResult Index()
{
    var events = Builder.CreateListOfSize(10).All().With(x=>x.Id = 1).And(x=> x.City = "Valencia").And(x=> x.Country = "Spain").And(x=> x.Venue = "L'umbracle").Build();
    return View(events);
}

O mejor aún, algo más elaborado, donde tendremos los primeros N elementos de una forma, los siguientes N de otra, y así sucesivamente:

public ActionResult Index()
{
    var events = Builder.CreateListOfSize(10).All().TheFirst(2).With( x=> x.Id = 1).TheNext(3).With( x=> x.City = "Madrid").TheNext(3).With(x=>x.Country = "USA").TheLast(2).With(x=>x.Venue = "Microsoft").Build();
    return View(events);
}

Por supuesto podéis usar NBuilder en otros proyectos, así como con tipos de datos complejos, pero…ya tenéis la idea 🙂


Cómo hacer que PointerPressed y PointerReleased funcionen en un botón de Windows 8 con C#

Hoy, haciendo algunas pruebas para el trabajo, tuve la necesidad de detectar sólo cuando el usuario está -activamente- presionando un botón. Es decir, no el evento click/tap ni un hold, sólo cuando tiene el click/dedo encima del botón mientras lo mantiene presionado. Esto se corresponde con los eventos PointerPressed y PointerReleased de los elementos visuales.

El problema, en Windows 8, está en que estos eventos no se disparan correctamente en un botón, directamente no se disparan. Algo disparatado, realmente, teniendo en cuenta que es un elemento visual que está hecho específicamente para ser presionado. Sea como fuere, puede haber dos soluciones:

  1. Si necesitas varios botones pulsados a la vez entonces, la solución no es usar un botón, sino un Grid, como bien me sugirió Adrián Fernández.
  2. Si sólo necesitas un botón a la vez, la solución pasa por hacerlo de una manera más rústica que sólo suscribiéndose al PointerPressed y PointerReleased, con el siguiente código:
BotonQueNecesites.AddHandler(PointerPressedEvent, new PointerEventHandler(BotonQueNecesites_PointerPressed), true);

Recuerda que puedes usar Ctrl+. sobre BotonQueNecesites_PointerPressed para poder crear el método con los argumentos correctos, sin necesidad de sabértelos de memoria. Así pues, el caso contrario par el PointerReleased resulta análogo:

BotonQueNecesites.AddHandler(PointerReleasedEvent, new PointerEventHandler(BotonQueNecesites_PointerReleased), true);

A partir de esto, sólo queda definir los handlers:

private void BotonQueNecesites_PointerReleased(object sender, PointerRoutedEventArgs e)
{
     output.Text = "Released";
}

private void BotonQueNecesites_PointerPressed(object sender, PointerRoutedEventArgs e)
{
     output.Text = "Pressed";
}

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 incluir la política de privacidad en opciones de configuración de la aplicación en Windows 8

Hace unos días estaba subiendo una de mis primeras aplicaciones para la Windows 8 Store, así que, antes de subirla, decidí darle un último repaso a las exigencias de la Store, entonces recordé algo que vi en el MSP Summit de Madrid de la semana pasada: si usas Internet en tu app, debes incluir una política de privacidad dentro del menú de configuración de tu aplicación, enmarcada dentro de la barra de charms. Tras una búsqueda rápida por los foros de MSDN no encontré nada, así que, yendo a algo más potente, estos son los resultados de la búsqueda. Vamos allá:

Primero hemos de crear una web donde dejemos clara nuestra política de privacidad. En mi caso, la aplicación no recogía datos de usuario de ningún tipo, sencillamente usaba Internet para mostrar información, así que tomé uno cualquiera de ejemplo y lo modifiqué, quedando así. Guarda el link a tu página, ya que tendrás que ponerlo cuando subas la aplicación a la store.

Segundo vamos al código. La mecánica es sencilla, nos suscribimos al evento CommandsRequested de la vista actual en otro evento que tengamos o método. Lo suyo es hacerlo en el OnNavigatedTo o LoadState, aunque me gusta separarlos y usarlos acorde a su nombre. Así que, al OnNavigatedTo:


protected override void OnNavigatedTo(NavigationEventArgs e){

SettingsPane.GetForCurrentView().CommandsRequested += principal_CommandsRequested;

}

Después de esto el paso es obvio, crear el principal_CommandsRequested :


void principal_CommandsRequested(SettingsPane sender, SettingsPaneCommandsRequestedEventArgs args){

AddSettingsCommands(args);

}

¿Alguien duda del siguiente paso?


public static void AddSettingsCommands(SettingsPaneCommandsRequestedEventArgs args)

{

args.Request.ApplicationCommands.Clear();

SettingsCommand privacyPref = new SettingsCommand("privacyPref", "Política de Privacidad", (uiCommand) =>{Windows.System.Launcher.LaunchUriAsync(newUri("http://danielrozo.es/privacy-policy")); });

args.Request.ApplicationCommands.Add(privacyPref);

}

Y con esto debería estar todo hecho, compiláis y corréis la aplicación y veréis cómo sale “Política de Privacidad” bajo la configuración de vuestra aplicación y cuando hagáis clic os llevará a la página que hayáis configurado al efecto :).


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 😉