2010-02-21 8 views
11

Estoy escribiendo una aplicación de WPF 4 (con VS2010 RC) usando MVVM Light V3 alpha 3 y estoy corriendo en algún comportamiento extraño aquí ...CanExecute en RelayCommand <T> no trabajar

tengo un comando que abre una Window, y esa ventana crea el ViewModel, etc., nada raro allí.

En ese Window Tengo algunas RelayCommand s, por ejemplo:

CategoryBeenSelected = new RelayCommand(() => OnCategoryUpdate = true); 

Nada raro otra vez - que funciona como esperaba.

El problema es que no puedo tener un método CanExecute/expresión lambda con un RelayCommand genérico.

Esto funciona:

DeleteCategoryCommand = new RelayCommand<int>(DeleteCategory); 

Pero esto no es así:

DeleteCategoryCommand = new RelayCommand<int>(DeleteCategory, CanDeleteCategory); 

La ventana no aparece. Es decir, hacer clic en el botón que abre la ventana, y la aplicación sólo se bloquea y unos segundos más tarde, el método de la ventana InitializeComponent lanza una (Referencia a objeto no establecida como instancia de un objeto) NullReferenceException

En resumen, si Puse un método CanExecute en un RelayCommand<T>, Window que posee que no se puede crear una instancia de ViewModel (con el RelayCommand<T>). Si elimino el CanExecute, aparece el Window.

¿Dónde está el problema aquí? Estoy confundido.

Gracias.

EDIT: Conforme a lo solicitado, aquí está el seguimiento de la pila:

 
A first chance exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll 
    at GalaSoft.MvvmLight.Command.RelayCommand`1.CanExecute(Object parameter) 
    at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute() 
    at System.Windows.Controls.Primitives.ButtonBase.OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 
    at System.Windows.DependencyObject.OnPropertyChanged(DependencyPropertyChangedEventArgs e) 
    at System.Windows.FrameworkElement.OnPropertyChanged(DependencyPropertyChangedEventArgs e) 
    at System.Windows.DependencyObject.NotifyPropertyChange(DependencyPropertyChangedEventArgs args) 
    at System.Windows.DependencyObject.UpdateEffectiveValue(EntryIndex entryIndex, DependencyProperty dp, PropertyMetadata metadata, EffectiveValueEntry oldEntry, EffectiveValueEntry& newEntry, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType) 
    at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value, PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue, OperationType operationType, Boolean isInternal) 
    at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value) 
    at MS.Internal.Xaml.Runtime.ClrObjectRuntime.SetValue(Object inst, XamlMember property, Object value) 
    at MS.Internal.Xaml.Runtime.PartialTrustTolerantRuntime.SetValue(Object obj, XamlMember property, Object value) 
    at System.Xaml.XamlObjectWriter.Logic_ApplyPropertyValue(ObjectWriterContext ctx, XamlMember prop, Object value, Boolean onParent) 
    at System.Xaml.XamlObjectWriter.Logic_DoAssignmentToParentProperty(ObjectWriterContext ctx) 
    at System.Xaml.XamlObjectWriter.WriteEndObject() 
    at System.Windows.Markup.WpfXamlLoader.TransformNodes(XamlReader xamlReader, XamlObjectWriter xamlWriter, Boolean onlyLoadOneNode, Boolean skipJournaledProperties, Boolean shouldPassLineNumberInfo, IXamlLineInfo xamlLineInfo, IXamlLineInfoConsumer xamlLineInfoConsumer, XamlContextStack`1 stack, IStyleConnector styleConnector) 
    at System.Windows.Markup.WpfXamlLoader.Load(XamlReader xamlReader, IXamlObjectWriterFactory writerFactory, Boolean skipJournaledProperties, Object rootObject, XamlObjectWriterSettings settings, Uri baseUri) 
    at System.Windows.Markup.WpfXamlLoader.LoadBaml(XamlReader xamlReader, Boolean skipJournaledProperties, Object rootObject, XamlAccessLevel accessLevel, Uri baseUri) 
    at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream) 
    at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator) 
    at ApuntaNotas.Views.CategoryEditorView.InitializeComponent() in c:\Users\Jesus\Documents\Visual Studio 2010\Projects\ApuntaNotas\ApuntaNotas\Views\CategoryEditorView.xaml:line 1 
    at ApuntaNotas.Views.CategoryEditorView..ctor() in C:\Users\Jesus\Documents\Visual Studio 2010\Projects\ApuntaNotas\ApuntaNotas\Views\CategoryEditorView.xaml.cs:line 18 
A first chance exception of type 'System.NullReferenceException' occurred in PresentationFramework.dll 
+1

¿Tal vez podría adjuntar un rastro de pila? Podría ayudar a entender qué salió mal. – Vlad

+0

Lo siento, lo olvidé, ahí está :) –

+0

Es extraño: Reflector dice que la función 'CanExecute' se define de tal manera:' public bool CanExecute (parámetro de objeto) {return (this._canExecute == null) | | this._canExecute ((T) parámetro)); } ' No hay nada para arrojar una excepción. – Vlad

Respuesta

7

Parece que el RelayCommand arrojará el valor del parámetro a la T. genérica

Pero no se puede echar un valor nulo a una estructura, como la excepción que dice!

Si inicializa el RelayCommand con una estructura con nulos, ¡funcionará como se esperaba!

RelayCommand<int?> or RelayCommand<Nullable<int>> 

HTH

+0

Uhm, esa debería ser la razón ... Pero es un poco raro ... No vi ningún código usando nullable ... –

+0

Sí, eso es correcto ... un 'double' o' int' son tipos de valores, y no puede ser nulo Si los convierte en tipos anulables, debería funcionar. ¡Casting 'null' para una struct producirá una excepción! ¡Vea el comentario de Vlads con el método donde puede ver el lanzamiento a T! – Arcturus

+0

intente compilar doble prueba = (doble) nulo; .. en el mundo genérico obtendrá una excepción de tiempo de ejecución! ;) – Arcturus

0

Tal vez, en este momento, el parámetro es null?

2

Arcturus era correcta en la identificación de cuál era el problema, sin embargo, no me gusta la solución de utilizar primitivas anulables. Personalmente, no me gustan los primitivos anulables a menos que tenga una muy buena razón para usarlos.

En cambio, he cambiado la implementación de RelayCommand de la siguiente manera:

bool ICommand.CanExecute(object parameter) 
    { 
     if (parameter == null && typeof(T).IsValueType) 
     { 
      return CanExecute(default(T)); 
     } 
     return CanExecute((T)parameter); 
    } 

no hice este mismo cambio para el método genérico Ejecutar (al menos por ahora), ya que no creo que no es razonable fallar en ese caso si el comando realmente espera un argumento.

El problema con CanExecute es que el sistema WPF a veces lo llama antes de que se puedan evaluar ciertos enlaces. Por ejemplo:

 <Button Content="Fit To Width" Command="{Binding Path=FitToWidthCommand}" CommandParameter="{Binding ElementName=imageScrollViewer, Path=ActualWidth}" /> 
     <Button Content="Fit To Height" Command="{Binding Path=FitToHeightCommand}" CommandParameter="{Binding ElementName=imageScrollViewer, Path=ActualHeight}" /> 

En el XAML anterior, se observa el parámetro de comando se une a la anchura real de un control. Sin embargo, WPF llamará a CanExecute en el comando del botón antes de que el control "imageScrollViewer" esté necesariamente distribuido/renderizado, por lo que no hay ancho/alto real. En el momento en que el usuario hace clic en el botón y se invoca Ejecutar, por supuesto, el control se establece para que los valores se envíen al comando. De lo contrario, creo que no es lo que debería esperarse, pero solo cuando el usuario hace clic en el botón.

Por supuesto, no me gusta el comportamiento diferente de CanExecute y Execute, pero por ahora parece ajustarse a las restricciones presentadas por el marco. Es posible que encuentre una situación en la que esto me cause dolor, pero hasta ahora me ha gustado el cambio.