2010-02-21 6 views
11

Sto scrivendo un'applicazione WPF 4 (con VS2010 RC) utilizzando MVVM Luce V3 alpha 3 e sono in esecuzione in un comportamento strano qui ...CanExecute su RelayCommand <T> non funziona

Ho un comando che apre un Window, e che Window crea il ViewModel e così via - niente di strano lì.

In quel Window mi hanno alcuni RelayCommand s, per esempio:

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

Niente di strano ancora una volta - che funziona come mi aspettavo.

Il problema è che non riesco ad avere un'espressione di metodo/lambda CanExecute con un RelayCommand generico.

Questo funziona:

DeleteCategoryCommand = new RelayCommand<int>(DeleteCategory); 

Ma questo non lo fa:

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

La finestra non si presenta. Voglio dire, si fa clic sul pulsante che apre la finestra, e l'applicazione appena viene bloccato e alcuni secondi dopo, il metodo della finestra InitializeComponent getta un (riferimento non impostato a un'istanza di un oggetto) NullReferenceException

In breve, se Ho inserito un metodo su un RelayCommand<T>, il Window che possiede che ViewModel (con RelayCommand<T>) non può essere istanziato. Se rimuovo lo CanExecute, viene visualizzato lo Window.

Dov'è il problema qui? Non ho capito bene.

Grazie.

EDIT: Come richiesto, ecco la traccia dello stack:

 
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

Forse potresti attaccare una traccia dello stack? Potrebbe aiutare a capire cosa è andato storto. – Vlad

+0

Scusa, ho dimenticato che, eccolo :) –

+0

È strano: Reflector dice che la funzione 'CanExecute' è definita in questo modo:' public bool CanExecute (parametro oggetto) {return (this._canExecute == null) | | this._canExecute ((T) parametro)); } '. Non c'è nulla da gettare un'eccezione. – Vlad

risposta

7

Sembra che il RelayCommand lancerà il valore del parametro al T. generica

Ma non si può lanciare un valore nullo ad una struttura, come l'eccezione che si dice!

Se si inizializza RelayCommand con una struttura nullable, funzionerà come previsto!

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

HTH

+0

Uhm, questa dovrebbe essere la ragione ... Ma è un po 'strano .. Non ho visto alcun codice usando nullable ... –

+0

Sì, è corretto .. a 'double' o' int' sono tipi di valore, e non può essere nullo. Se li rendi tipi nullable, dovrebbe funzionare. Il lancio di 'null' su una struttura produrrà un'eccezione! Vedi il commento di Vlads con il metodo in cui puoi vedere il cast in T! – Arcturus

+0

prova a compilare double test = (double) null; .. nel mondo generico otterrai un'eccezione di runtime! ;) – Arcturus

0

Forse, in questo momento, il parametro è null?

2

Arcturus era corretto nel identificare quale fosse il problema, ma non mi piaceva la soluzione di utilizzare primitive nullable. Personalmente non mi piacciono le primitive nullable a meno che non abbia una buona ragione per usarle.

Invece, ho cambiato l'attuazione di RelayCommand come segue:

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

non ho fatto questa stessa modifica per il metodo Execute generico (almeno per ora), perché non penso che è irragionevole fallire in quel caso se il comando si aspetta davvero un argomento.

Il problema con CanExecute è che il sistema WPF a volte lo chiama prima che determinati bind possano essere valutati. Ad esempio:

 <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}" /> 

Nel suddetto XAML, si nota che il parametro di comando è associato alla larghezza effettiva di un controllo. Tuttavia, WPF chiamerà CanExecute sul comando del pulsante prima che il controllo "imageScrollViewer" sia necessariamente disposto/sottoposto a rendering - quindi non esiste una larghezza/altezza effettiva. Nel momento in cui l'utente fa clic sul pulsante e viene richiamato Execute, ovviamente il controllo viene impostato in modo che i valori vengano inviati al comando. In caso contrario, penso che il fallimento sia ciò che dovrebbe essere previsto, ma solo quando l'utente fa effettivamente clic sul pulsante.

Naturalmente non mi piace il diverso comportamento di CanExecute ed Execute, ma per ora sembra adattarsi alle restrizioni presentate dal framework. Potrei trovare uno scenario in cui questo mi provoca dolore, ma mi è piaciuto il cambiamento finora.