2010-02-21 5 views
11

Я пишу WPF 4 приложения (с VS2010 RC) с использованием MVVM Light V3 альфа 3 и бегом в какое-то странное поведение здесь ...CanExecute на RelayCommand <T> не работает

У меня есть команда, которая открывает Window, и это окно создает ViewModel и т. Д. - там ничего странного.

В этом Window У меня несколько RelayCommand с, например:

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

Ничего странного снова - он работает, как я ожидал.

Проблема в том, что у меня не может быть метода CanExecute/лямбда-выражения с общим RelayCommand.

Это работает:

DeleteCategoryCommand = new RelayCommand<int>(DeleteCategory); 

Но это не делает:

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

Окно не отображается. Я имею в виду, я нажимаю кнопку, которая открывает окно, и приложение просто блокируется, и через несколько секунд, InitializeComponent метод окна бросков (ссылка на объект не указывает на экземпляр объекта) в NullReferenceException

Короче говоря, если Я положил метод CanExecute на RelayCommand<T>, Window, что принадлежит, что ViewModel (с RelayCommand<T>) не может быть создан. Если я удалю CanExecute, появится сообщение Window.

Где находится проблема? Я смущен.

спасибо.

EDIT: В соответствии с просьбой, вот трассировки стека:

 
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

Возможно, вы можете приложить трассировку стека? Это может помочь понять, что пошло не так. – Vlad

+0

Извините, я это забыл :) –

+0

Странно: Reflector сообщает, что функция 'CanExecute' определена таким образом:' public bool CanExecute (object parameter) {return (this._canExecute == null) | | this._canExecute ((T) параметр)); } '. Нечего исключать. – Vlad

ответ

7

Кажется, что RelayCommand будет привести значение параметра в общей Т.

Но вы не можете бросить пустой указатель на структуру, так как исключение говорит вам!

Если вы инициализируете RelayCommand с помощью NULL-структуры, она будет работать так, как ожидалось!

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

НТН

+0

Ухм, это должно быть причиной ... Но это немного странно .. Я не видел никакого кода с использованием nullable ... –

+0

Да, это правильно .. 'double' или' int' являются типами значений, и не может быть нулевым. Если вы делаете их типами с нулевым значением, он должен работать. Приведение 'null' в структуру создаст исключение! См. Комментарий Влада с методом, где вы можете увидеть приведение к T! – Arcturus

+0

попробуйте выполнить компиляцию double test = (double) null; .. в общем мире вы получите исключение во время выполнения! ;) – Arcturus

2

Арктур ​​был прав в определении того, что проблема была, но я не хотел решения с использованием NULLABLE примитивов. Мне лично не нравятся примитивы с нулевыми значениями, если у меня нет оснований для их использования.

Вместо этого я изменил реализацию RelayCommand следующим образом:

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

Я не делал этого же изменения для общего Execute метода (по крайней мере сейчас), потому что я не думаю, что это неразумно для отказа в этом случае, если команда действительно ожидает аргумент.

Проблема с CanExecute заключается в том, что система WPF иногда вызывает ее, прежде чем можно будет определить определенные привязки. Например:

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

В приведенном выше XAML вы замечаете, что параметр команды привязан к фактической ширине элемента управления. Однако WPF будет вызывать CanExecute по команде кнопки до того, как элемент управления imageScrollViewer обязательно будет выложен/отображен - поэтому фактической ширины/высоты нет. Когда пользователь нажимает кнопку и вызывается Execute, конечно, элемент управления выставляется так, что значения передаются команде. Если нет - я думаю, что неудача - это то, что следует ожидать - но только тогда, когда пользователь на самом деле нажимает кнопку.

Конечно, мне не нравится различное поведение CanExecute и Execute, но на данный момент оно, похоже, соответствует ограничениям, представленным каркасом. Я могу найти сценарий, когда это вызывает у меня печаль, но до сих пор мне нравилось это изменение.