Estoy trabajando en una aplicación WPF con vistas que requieren numerosas conversiones de valor. Inicialmente, mi filosofía (inspirada en parte por este animado debate sobre los Discípulos XAML ) era que debía hacer el modelo de vista estrictamente sobre el soporte de los requisitos de datos de la vista. Esto significaba que cualquier conversión de valor requerida para convertir datos en cosas como visibilidades, pinceles, tamaños, etc. se manejaría con convertidores de valor y convertidores de valores múltiples. Conceptualmente, esto parecía bastante elegante. El modelo de vista y la vista tendrían un propósito distinto y estarían muy bien desacopladas. Se trazaría una línea clara entre "datos" y "aspecto".
Bueno, después de dar a esta estrategia "el viejo intento de la universidad", tengo algunas dudas sobre si quiero seguir desarrollándome de esta manera. De hecho, estoy considerando descartar los convertidores de valor y poner la responsabilidad de (casi) toda la conversión de valor directamente en manos del modelo de vista.
La realidad del uso de convertidores de valor simplemente no parece estar a la altura del valor aparente de las preocupaciones separadas por separado. Mi mayor problema con los convertidores de valor es que son tediosos de usar. Debe crear una nueva clase, implementar IValueConverter
o IMultiValueConverter
convertir el valor o valores object
al tipo correcto, probar DependencyProperty.Unset
(al menos para convertidores de valores múltiples), escribir la lógica de conversión, registrar el convertidor en un diccionario de recursos [consulte la actualización a continuación ], y finalmente, conecte el convertidor usando XAML bastante detallado (que requiere el uso de cadenas mágicas tanto para el enlace como para el nombre del convertidor[ver actualización a continuación]). El proceso de depuración tampoco es fácil, ya que los mensajes de error a menudo son crípticos, especialmente en el modo de diseño de Visual Studio / Expression Blend.
Esto no quiere decir que la alternativa, hacer que el modelo de vista sea responsable de toda la conversión de valor, sea una mejora. Esto podría muy bien ser una cuestión de que la hierba sea más verde en el otro lado. Además de perder la elegante separación de las preocupaciones, debe escribir un montón de propiedades derivadas y asegurarse de llamar concienzudamente RaisePropertyChanged(() => DerivedProperty)
al establecer las propiedades base, lo que podría ser un problema de mantenimiento desagradable.
La siguiente es una lista inicial que reuní de los pros y los contras de permitir que los modelos de vista manejen la lógica de conversión y eliminen los convertidores de valor:
- Pros:
- Menos enlaces totales ya que se eliminan los convertidores múltiples
- Menos cadenas mágicas (rutas de enlace
+ nombres de recursos del convertidor) No más registrar cada convertidor (además de mantener esta lista)- Menos trabajo para escribir cada convertidor (no se requieren interfaces de implementación ni conversión)
- Puede inyectar dependencias fácilmente para ayudar con las conversiones (por ejemplo, tablas de colores)
- El marcado XAML es menos detallado y más fácil de leer.
- La reutilización del convertidor aún es posible (aunque se requiere algo de planificación)
- No hay problemas misteriosos con DependencyProperty.Unset (un problema que noté con los convertidores de valores múltiples)
* Los tachados indican beneficios que desaparecen si usa extensiones de marcado (consulte la actualización a continuación)
- Contras:
- Un acoplamiento más fuerte entre el modelo de vista y la vista (por ejemplo, las propiedades deben tratar conceptos como visibilidad y pinceles)
- Más propiedades totales para permitir la asignación directa para cada enlace a la vista
(consulte la Actualización 2 a continuación)RaisePropertyChanged
debe llamarse para cada propiedad derivada- Todavía debe confiar en los convertidores si la conversión se basa en una propiedad de un elemento de la interfaz de usuario
Entonces, como probablemente pueda notar, tengo un poco de acidez estomacal sobre este problema. Dudo mucho en seguir el camino de la refactorización solo para darme cuenta de que el proceso de codificación es tan ineficiente y tedioso si uso convertidores de valor o expongo numerosas propiedades de conversión de valor en mi modelo de vista.
¿Me estoy perdiendo algún pros / contras? Para aquellos que han intentado ambos medios de conversión de valor, ¿cuál encontró que funcionó mejor para usted y por qué? ¿Hay otras alternativas? (Los discípulos mencionaron algo sobre los proveedores de descriptores de tipo, pero no pude entender de qué estaban hablando. Cualquier idea sobre esto sería apreciada).
Actualizar
Hoy descubrí que es posible usar algo llamado "extensión de marcado" para eliminar la necesidad de registrar convertidores de valor. De hecho, no solo elimina la necesidad de registrarlos, sino que también proporciona inteligencia para seleccionar un convertidor cuando escribe Converter=
. Aquí está el artículo que me ayudó a comenzar: http://www.wpftutorial.net/ValueConverters.html .
La capacidad de usar una extensión de marcado cambia el equilibrio de alguna manera en mi lista de pros y contras y en la discusión anterior (ver tachado).
Como resultado de esta revelación, estoy experimentando con un sistema híbrido donde utilizo convertidores BoolToVisibility
y lo que llamo MatchToVisibility
y el modelo de vista para todas las demás conversiones. MatchToVisibility es básicamente un convertidor que me permite verificar si el valor enlazado (generalmente una enumeración) coincide con uno o más valores especificados en XAML.
Ejemplo:
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue=Visible, IfFalse=Hidden, Value1=Finished, Value2=Canceled}}"
Básicamente, lo que hace es verificar si el estado es Finalizado o Cancelado. Si es así, la visibilidad se establece en "Visible". De lo contrario, se establece en "Oculto". Esto resultó ser un escenario muy común, y tener este convertidor me ahorró alrededor de 15 propiedades en mi modelo de vista (más las declaraciones RaisePropertyChanged asociadas). Tenga en cuenta que cuando escribe Converter={vc:
, "MatchToVisibility" aparece en un menú inteligente. Esto reduce notablemente la posibilidad de errores y hace que el uso de convertidores de valor sea menos tedioso (no tiene que recordar ni buscar el nombre del convertidor de valor que desea).
En caso de que tenga curiosidad, pegaré el código a continuación. Una característica importante de esta implementación de MatchToVisibility
es que se comprueba si el valor límite es una enum
, y si lo es, se comprueba para asegurarse Value1
, Value2
, etc., también son enumeraciones del mismo tipo. Esto proporciona una verificación en tiempo de diseño y tiempo de ejecución de si alguno de los valores de enumeración está mal escrito. Para mejorar esto a una verificación en tiempo de compilación, puede usar lo siguiente (escribí esto a mano, así que perdóneme si cometí algún error):
Visibility="{Binding Status, Converter={vc:MatchToVisibility
IfTrue={x:Type {win:Visibility.Visible}},
IfFalse={x:Type {win:Visibility.Hidden}},
Value1={x:Type {enum:Status.Finished}},
Value2={x:Type {enum:Status.Canceled}}"
Si bien esto es más seguro, es demasiado detallado para que valga la pena para mí. También podría usar una propiedad en el modelo de vista si voy a hacer esto. De todos modos, descubro que la verificación en tiempo de diseño es perfectamente adecuada para los escenarios que he probado hasta ahora.
Aquí está el código para MatchToVisibility
[ValueConversion(typeof(object), typeof(Visibility))]
public class MatchToVisibility : BaseValueConverter
{
[ConstructorArgument("ifTrue")]
public object IfTrue { get; set; }
[ConstructorArgument("ifFalse")]
public object IfFalse { get; set; }
[ConstructorArgument("value1")]
public object Value1 { get; set; }
[ConstructorArgument("value2")]
public object Value2 { get; set; }
[ConstructorArgument("value3")]
public object Value3 { get; set; }
[ConstructorArgument("value4")]
public object Value4 { get; set; }
[ConstructorArgument("value5")]
public object Value5 { get; set; }
public MatchToVisibility() { }
public MatchToVisibility(
object ifTrue, object ifFalse,
object value1, object value2 = null, object value3 = null,
object value4 = null, object value5 = null)
{
IfTrue = ifTrue;
IfFalse = ifFalse;
Value1 = value1;
Value2 = value2;
Value3 = value3;
Value4 = value4;
Value5 = value5;
}
public override object Convert(
object value, Type targetType, object parameter, CultureInfo culture)
{
var ifTrue = IfTrue.ToString().ToEnum<Visibility>();
var ifFalse = IfFalse.ToString().ToEnum<Visibility>();
var values = new[] { Value1, Value2, Value3, Value4, Value5 };
var valueStrings = values.Cast<string>();
bool isMatch;
if (Enum.IsDefined(value.GetType(), value))
{
var valueEnums = valueStrings.Select(vs => vs == null ? null : Enum.Parse(value.GetType(), vs));
isMatch = valueEnums.ToList().Contains(value);
}
else
isMatch = valueStrings.Contains(value.ToString());
return isMatch ? ifTrue : ifFalse;
}
}
Aquí está el código para BaseValueConverter
// this is how the markup extension capability gets wired up
public abstract class BaseValueConverter : MarkupExtension, IValueConverter
{
public override object ProvideValue(IServiceProvider serviceProvider)
{
return this;
}
public abstract object Convert(
object value, Type targetType, object parameter, CultureInfo culture);
public virtual object ConvertBack(
object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
Aquí está el método de extensión ToEnum
public static TEnum ToEnum<TEnum>(this string text)
{
return (TEnum)Enum.Parse(typeof(TEnum), text);
}
Actualización 2
Desde que publiqué esta pregunta, me encontré con un proyecto de código abierto que usa "tejido de IL" para inyectar código NotifyPropertyChanged para propiedades y propiedades dependientes. Esto hace que implementar la visión de Josh Smith del modelo de vista como un "convertidor de valor con esteroides" sea una brisa absoluta. Simplemente puede usar "Propiedades de implementación automática", y el tejedor hará el resto.
Ejemplo:
Si ingreso este código:
public string GivenName { get; set; }
public string FamilyName { get; set; }
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
... esto es lo que se compila:
string givenNames;
public string GivenNames
{
get { return givenName; }
set
{
if (value != givenName)
{
givenNames = value;
OnPropertyChanged("GivenName");
OnPropertyChanged("FullName");
}
}
}
string familyName;
public string FamilyName
{
get { return familyName; }
set
{
if (value != familyName)
{
familyName = value;
OnPropertyChanged("FamilyName");
OnPropertyChanged("FullName");
}
}
}
public string FullName
{
get
{
return string.Format("{0} {1}", GivenName, FamilyName);
}
}
Eso es un gran ahorro en la cantidad de código que tiene que escribir, leer, desplazarse, etc. Más importante, sin embargo, le ahorra tener que averiguar cuáles son sus dependencias. Puede agregar nuevas "propiedades obtiene" comoFullName
sin tener que subir minuciosamente la cadena de dependencias para agregar RaisePropertyChanged()
llamadas.
¿Cómo se llama este proyecto de código abierto? La versión original se llama "NotifyPropertyWeaver", pero el propietario (Simon Potter) ha creado una plataforma llamada "Fody" para alojar una serie completa de tejedores IL. El equivalente de NotifyPropertyWeaver bajo esta nueva plataforma se llama PropertyChanged.Fody.
- Instrucciones de configuración de Fody: http://code.google.com/p/fody/wiki/SampleUsage (reemplace "Virtuosity" con "PropertyChanged")
- Sitio del proyecto PropertyChanged.Fody: http://code.google.com/p/propertychanged/
Si prefiere usar NotifyPropertyWeaver (que es un poco más simple de instalar, pero no necesariamente se actualizará en el futuro más allá de las correcciones de errores), aquí está el sitio del proyecto: http://code.google.com/p/ notifiquepropiedadweaver /
De cualquier manera, estas soluciones de tejido IL cambian completamente el cálculo en el debate entre el modelo de vista sobre esteroides y los convertidores de valor.
MatchToVisibility
parecía ser una forma conveniente de habilitar algunos interruptores de modo simples (tengo una vista en particular con un montón de partes que se pueden encender y apagar. En la mayoría de los casos, las secciones de la vista incluso están etiquetadas (con x:Name
) para que coincidan con el modo corresponden a). Realmente no se me ocurrió que esto es "lógica de negocios", pero pensaré en su comentario.
BooleanToVisibility
toma un valor relacionado con la visibilidad (verdadero / falso) y lo traduce a otro. Esto parece un uso ideal de aValueConverter
. Por otro lado,MatchToVisibility
está codificando la lógica de negocios enView
(qué tipos de elementos deberían estar visibles). En mi opinión, esta lógica debería ser empujada hacia abajoViewModel
, o incluso más allá de lo que yo llamoEditModel
. Lo que el usuario puede ver debería ser algo bajo prueba.