pátek 27. září 2013

Vylepšená třída PropertySupport z knihovny Prism

Microsoft nabízí na stránkách Codeplexu užitečnou knihovnu PRISM, kterou jistě každý WPFkový vývojář zná (ať už ji využívá nebo ne). Obsahuje šikovnou helper třídu PropertySupport s jedinou metodou ExtractPropertyName, která umí na základě zadané expresion vrátit název property ve třídě. Pokud se potřebujete v kódu odkázat na jméno property, tak to lze právě díky výše uvedené metodě udělat čistě bez nutnosti zanášet zdroják stringovými zápisy.

public class A {
    public string Foo { get; set; }

    public void Test() {
        System.Console.WriteLine("Trida A obsahuje propertu " + PropertySupport.ExtractPropertyName(() => Foo));

        var propertyName = "Foo";
        System.Console.WriteLine("Trida A obsahuje propertu " + propertyName)
    }
}

Nevýhodou je, že se takto dokážete odkázat pouze na property z třídy, ve které se na ní ptáte. Existuje ale možnost přidat overload metodu, která by uměla vrátit jméno property z libovolné třídy. Bohužel vývojářům PRISMu se tato nová metoda moc nelíbí, připadá jim, že porušuje Single Responsibility princip, takže nezbývá, než napsat vlastní třídu PropertySupport a tu si rozšířit dle potřeby.

public class PropertySupport
{
    public static string ExtractPropertyName<T>(Expression<Func<T>> propertyExpression)
    {
        if (propertyExpression == null) {
            throw new ArgumentNullException("propertyExpression");
        }

        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null) {
            throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
        }

        var property = memberExpression.Member as PropertyInfo;
        if (property == null) {
            throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
        }

        var getMethod = property.GetGetMethod(true);
        if (getMethod.IsStatic) {
            throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
        }

        return memberExpression.Member.Name;
    }

    public static string ExtractPropertyName<TPropertyType, TClassType>(Expression<Func<TClassType, TClassType, TPropertyType>> propertyExpression)
    {
        if (propertyExpression == null) {
            throw new ArgumentNullException("propertyExpression");
        }

        var memberExpression = propertyExpression.Body as MemberExpression;
        if (memberExpression == null) {
            throw new ArgumentException("The expression is not a member access expression.", "propertyExpression");
        }

        var property = memberExpression.Member as PropertyInfo;
        if (property == null) {
            throw new ArgumentException("The member access expression does not access a property.", "propertyExpression");
        }

        var getMethod = property.GetGetMethod(true);
        if (getMethod.IsStatic) {
            throw new ArgumentException("The referenced property is a static property.", "propertyExpression");
        }

        return memberExpression.Member.Name;
    }
}

Poté je možné využít novou overload metodu následujícím způsobem. Jistě uznám, není to tak přímočarý a jednoduchý zápis jako v prvním příkladě, ale jednoduchost není všechno, že :-).

public class A {
    public string Foo { get; set; }

    public void Test() {
        System.Console.WriteLine("Trida A obsahuje propertu " + PropertySupport.ExtractPropertyName(() => Foo));

        System.Console.WriteLine("Trida B obsahuje propertu " + PropertySupport.ExtractPropertyName((x, y) => x.Boo))
    }
}

public class B {
    public string Boo { get; set; }
}

Třeba se někomu takové řešení hodí, stejně jako mě.

středa 25. září 2013

Procházení VisualTree

Pokud potřebujete ve WPF pracovat s kontroly, máte k dispozici dva stromy - logický a vizuální, se kterými můžete pracovat. Zjednodušeně se dá říci, že:

  • Logický strom reprezentuje základní strukturu kontrolů, které jste si vytvořili v XAML či jinak.
  • Vizuální strom pak reprezentuje kompletní sadu která se vyyrenderuje na daném zobrazovacím zařízení.

Pokud tedy chcete něco najít, je vhodnější sáhnout po vizuálním stromu a tento prohledávat. Vzhledem k tomu, že (snad) všechny kontroly jsou odděděné od DependencyObject tak používám k tomuto účelu třídu s extension metodami, které injectují potřebnou funckionalitu přímo do třídy DependencyObject a jsou tak kdykoliv k dispozici.

Pozn. Zvykl jsem si umísťovat veškeré své extension metody do stejného namespace jako je cílová třída, do které se injektují. Uznávám, že to není úplně čisté řešení - může dojít ke konfliktům, při přechodu na novější verzi .NET, pokud by Microsoft použil stejně pojmenované metody. Ale jsem líný (asi jako každý programátor) a vyhovuje mi, že mohu používat extensions kdekoliv bez toho abych si musel vzpomenout, jakou namespace musím přidat :-).


namespace System.Windows
{
    public static class DependencyObjectExtensions
    {

        public static T FindFirstVisualChild<T>(this DependencyObject current) where T : DependencyObject
        {
            if (current == null) return null;
            var children = new List<DependencyObject> { current };
            var currentIndex = 0;
            while (currentIndex < children.Count)
            {
                current = children[currentIndex];
                for (var i = 0; i < VisualTreeHelper.GetChildrenCount(current); i++) {
                    var child = VisualTreeHelper.GetChild(current, i);
                    if (child is T) {
                        return (T) child;
                    }
                    children.Add(child);
                }
                currentIndex++;
            }
            return null;
        }

        public static T FindVisualParent<T>(this DependencyObject element) where T : DependencyObject
        {
            var parent = element;
            while (parent != null) {
                if (parent is T) {
                    return (T)parent;
                }
                parent = VisualTreeHelper.GetParent(parent);
            }
            return null;
        }
        
    }
}

Pokud někde v kódu potřebujete najít rodiče nebo potomka (prvního při hledání do šířky) daného typu, stačí provolat výše uvedené metody. Daly by se zapsat i elegantněji přes rekurzi, ale v době psaní jsem měl zrovna náladu na toto řešení :-).


pondělí 23. září 2013

Jednodušší práce s convertery v XAMLu

Při psaní XAMLu je občas otravné definovat použité convertery v resources a odkazovat se na ně. Může se hodit následující bázový converter, od kterého oddědíte nově vytvářený:


[MarkupExtensionReturnType(typeof(IValueConverter))]
public abstract class BaseValueConverter<TValueConverter> : MarkupExtension, IValueConverter 
    where TValueConverter : IValueConverter, new()
{
    public abstract object Convert(object value, Type targetType, object parameter, CultureInfo culture);
    public abstract object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture);
    
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new TValueConverter();
    }
}
    
[MarkupExtensionReturnType(typeof(IMultiValueConverter))]
public abstract class BaseMultiValueConverter<TValueConverter> : MarkupExtension, IMultiValueConverter
    where TValueConverter : IMultiValueConverter, new()
{
    public abstract object Convert(object[] values, Type targetType, object parameter, CultureInfo culture);
    public abstract object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture);
    
    public override object ProvideValue(IServiceProvider serviceProvider)
    {
        return new TValueConverter();
    }
}

Následně díky odděděné MarkupExtension můžete přímo odkazovat na nově vytvářenou instanci svého converteru v XAMLu takto:


<TextBox Text="{Binding Path=Value, Converter={conv:MyCustomConverter}" />

Při každém vyhodnocení bindingu se provolá metoda ProvideValue(...), která vrací novou instanci converteru.