Wystąpił problem z WPF i poleceniami, które są powiązane z przyciskiem wewnątrz DataTemplate elementu ItemsControl. Scenariusz jest dość prosty. ItemsControl jest powiązany z listą obiektów i chcę mieć możliwość usunięcia każdego obiektu z listy, klikając przycisk. Przycisk wykonuje polecenie, a polecenie dba o usunięcie. CommandParameter jest powiązany z obiektem, który chcę usunąć. W ten sposób wiem, co kliknął użytkownik. Użytkownik powinien mieć możliwość usuwania tylko swoich „własnych” obiektów - dlatego muszę sprawdzić wywołanie „CanExecute” polecenia, aby sprawdzić, czy użytkownik ma odpowiednie uprawnienia.
Problem polega na tym, że parametr przekazany do CanExecute ma wartość NULL przy pierwszym wywołaniu - więc nie mogę uruchomić logiki, aby włączyć / wyłączyć polecenie. Jeśli jednak włączę to zawsze, a następnie kliknę przycisk, aby wykonać polecenie, parametr CommandParameter zostanie przekazany poprawnie. Oznacza to, że działa powiązanie z CommandParameter.
XAML dla ItemsControl i DataTemplate wygląda następująco:
<ItemsControl
x:Name="commentsList"
ItemsSource="{Binding Path=SharedDataItemPM.Comments}"
Width="Auto" Height="Auto">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<Button
Content="Delete"
FontSize="10"
Command="{Binding Path=DataContext.DeleteCommentCommand, ElementName=commentsList}"
CommandParameter="{Binding}" />
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Jak widać, mam listę obiektów komentarzy. Chcę, aby CommandParameter z DeleteCommentCommand był powiązany z obiektem Command.
Więc wydaje mi się, że moje pytanie brzmi: czy ktoś wcześniej doświadczył tego problemu? CanExecute jest wywoływana przez moje polecenie, ale za pierwszym razem parametr ma zawsze wartość NULL - dlaczego tak jest?
Aktualizacja: udało mi się trochę zawęzić problem. Dodałem pusty Debug ValueConverter, aby móc wyprowadzić komunikat, gdy parametr CommandParameter jest powiązany z danymi. Okazuje się, że problem polega na tym, że metoda CanExecute jest wykonywana przed przypisaniem CommandParameter do przycisku. Próbowałem ustawić CommandParameter przed poleceniem (jak sugerowano) - ale nadal nie działa. Wszelkie wskazówki, jak to kontrolować.
Aktualizacja2: Czy istnieje sposób na wykrycie, kiedy powiązanie jest „gotowe”, aby wymusić ponowną ocenę polecenia? Ponadto - czy jest to problem, że mam wiele przycisków (po jednym dla każdego elementu w ItemsControl), które są powiązane z tym samym wystąpieniem obiektu polecenia?
Update3: Przesłałem reprodukcję błędu do mojego SkyDrive: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip
Odpowiedzi:
Natknąłem się na podobny problem i rozwiązałem go za pomocą mojego zaufanego TriggerConverter.
public class TriggerConverter : IMultiValueConverter { #region IMultiValueConverter Members public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) { // First value is target value. // All others are update triggers only. if (values.Length < 1) return Binding.DoNothing; return values[0]; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } #endregion }
Ten konwerter wartości przyjmuje dowolną liczbę parametrów i przekazuje pierwszy z nich z powrotem jako przekonwertowaną wartość. Kiedy jest używany w MultiBinding w twoim przypadku, wygląda to następująco.
<ItemsControl x:Name="commentsList" ItemsSource="{Binding Path=SharedDataItemPM.Comments}" Width="Auto" Height="Auto"> <ItemsControl.ItemTemplate> <DataTemplate> <StackPanel Orientation="Horizontal"> <Button Content="Delete" FontSize="10" CommandParameter="{Binding}"> <Button.Command> <MultiBinding Converter="{StaticResource TriggerConverter}"> <Binding Path="DataContext.DeleteCommentCommand" ElementName="commentsList" /> <Binding /> </MultiBinding> </Button.Command> </Button> </StackPanel> </DataTemplate> </ItemsControl.ItemTemplate> </ItemsControl>
Aby to zadziałało, musisz gdzieś dodać TriggerConverter jako zasób. Teraz właściwość Command jest ustawiana nie przed udostępnieniem wartości parametru CommandParameter. Możesz nawet powiązać z RelativeSource.Self i CommandParameter zamiast. aby osiągnąć ten sam efekt.
źródło
Miałem ten sam problem podczas próby powiązania z poleceniem w moim modelu widoku.
Zmieniłem to, aby używać względnego powiązania źródła zamiast odwoływania się do elementu według nazwy i to załatwiło sprawę. Powiązanie parametrów nie uległo zmianie.
Stary kod:
Nowy kod:
Aktualizacja : właśnie natknąłem się na ten problem bez używania ElementName, wiążę się z poleceniem w moim modelu widoku, a mój kontekst danych przycisku to mój model widoku. W tym przypadku musiałem po prostu przenieść atrybut CommandParameter przed atrybutem Command w deklaracji przycisku (w XAML).
źródło
CommandParameter
iCommand
przeraża mnie.Odkryłem, że kolejność, w której ustawiam Command i CommandParameter, robi różnicę. Ustawienie właściwości Command powoduje natychmiastowe wywołanie CanExecute, więc chcesz, aby CommandParameter był już ustawiony w tym momencie.
Odkryłem, że zmiana kolejności właściwości w XAML może faktycznie mieć wpływ, chociaż nie jestem pewien, czy rozwiąże Twój problem. Warto jednak spróbować.
Wydaje się, że sugerujesz, że przycisk nigdy nie zostanie włączony, co jest zaskakujące, ponieważ spodziewałbym się, że CommandParameter zostanie ustawiony wkrótce po właściwości Command w twoim przykładzie. Czy wywołanie CommandManager.InvalidateRequerySuggested () powoduje włączenie przycisku?
źródło
Wymyśliłem inną opcję obejścia tego problemu, którą chciałem się podzielić. Ponieważ metoda CanExecute polecenia jest wykonywana przed ustawieniem właściwości CommandParameter, utworzyłem klasę pomocniczą z dołączoną właściwością, która wymusza ponowne wywołanie metody CanExecute po zmianie powiązania.
A następnie na przycisku, do którego chcesz przypisać parametr polecenia ...
<Button Content="Press Me" Command="{Binding}" helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
Mam nadzieję, że to może pomóc komuś innemu w rozwiązaniu problemu.
źródło
To jest stary wątek, ale ponieważ Google przywiózł mnie tutaj, kiedy miałem ten problem, dodam to, co działało dla mnie dla DataGridTemplateColumn z przyciskiem.
Zmień oprawę z:
do
Nie wiem, dlaczego to działa, ale mi się udało.
źródło
Niedawno natknąłem się na ten sam problem (dla mnie dotyczyło to pozycji menu w menu kontekstowym) i chociaż może to nie być odpowiednie rozwiązanie w każdej sytuacji, znalazłem inny (i znacznie krótszy!) Sposób rozwiązania tego problemu problem:
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
Ignorując
Tag
obejście oparte na -bazie dla specjalnego przypadku menu kontekstowego, kluczem jest tutajCommandParameter
regularne wiązanie, ale wiązanieCommand
z dodatkowymIsAsync=True
. SpowodujeCanExecute
to nieco opóźnienie wiązania rzeczywistego polecenia (a tym samym jego wywołania), więc parametr będzie już dostępny. Oznacza to jednak, że przez krótką chwilę stan włączenia może być zły, ale w moim przypadku było to całkowicie do przyjęcia.źródło
Być może będziesz mógł użyć mojego,
CommandParameterBehavior
które opublikowałem wczoraj na forach Prism . Dodaje brakujące zachowanie, w którym zmianaCommandParameter
przyczynyCommand
ma być odpytywana.Jest tu pewna złożoność spowodowana przez moje próby uniknięcia wycieku pamięci spowodowanego dzwonieniem
PropertyDescriptor.AddValueChanged
bez późniejszego dzwonieniaPropertyDescriptor.RemoveValueChanged
. Próbuję to naprawić, wyrejestrowując handler, gdy ekement jest rozładowany.Prawdopodobnie będziesz musiał usunąć
IDelegateCommand
rzeczy, chyba że używasz Prism (i chcesz wprowadzić takie same zmiany jak ja w bibliotece Prism). Zauważ również, że generalnie nie używamyRoutedCommand
tutaj s (używamy PrismDelegateCommand<T>
do prawie wszystkiego), więc proszę nieCommandManager.InvalidateRequerySuggested
pociągaj mnie do odpowiedzialności, jeśli moje wezwanie do wywołania jakiejś kaskady zapaści fal kwantowych, która zniszczy znany wszechświat lub cokolwiek innego.using System; using System.ComponentModel; using System.Windows; using System.Windows.Input; namespace Microsoft.Practices.Composite.Wpf.Commands { /// <summary> /// This class provides an attached property that, when set to true, will cause changes to the element's CommandParameter to /// trigger the CanExecute handler to be called on the Command. /// </summary> public static class CommandParameterBehavior { /// <summary> /// Identifies the IsCommandRequeriedOnChange attached property /// </summary> /// <remarks> /// When a control has the <see cref="IsCommandRequeriedOnChangeProperty" /> /// attached property set to true, then any change to it's /// <see cref="System.Windows.Controls.Primitives.ButtonBase.CommandParameter" /> property will cause the state of /// the command attached to it's <see cref="System.Windows.Controls.Primitives.ButtonBase.Command" /> property to /// be reevaluated. /// </remarks> public static readonly DependencyProperty IsCommandRequeriedOnChangeProperty = DependencyProperty.RegisterAttached("IsCommandRequeriedOnChange", typeof(bool), typeof(CommandParameterBehavior), new UIPropertyMetadata(false, new PropertyChangedCallback(OnIsCommandRequeriedOnChangeChanged))); /// <summary> /// Gets the value for the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property. /// </summary> /// <param name="target">The object to adapt.</param> /// <returns>Whether the update on change behavior is enabled.</returns> public static bool GetIsCommandRequeriedOnChange(DependencyObject target) { return (bool)target.GetValue(IsCommandRequeriedOnChangeProperty); } /// <summary> /// Sets the <see cref="IsCommandRequeriedOnChangeProperty"/> attached property. /// </summary> /// <param name="target">The object to adapt. This is typically a <see cref="System.Windows.Controls.Primitives.ButtonBase" />, /// <see cref="System.Windows.Controls.MenuItem" /> or <see cref="System.Windows.Documents.Hyperlink" /></param> /// <param name="value">Whether the update behaviour should be enabled.</param> public static void SetIsCommandRequeriedOnChange(DependencyObject target, bool value) { target.SetValue(IsCommandRequeriedOnChangeProperty, value); } private static void OnIsCommandRequeriedOnChangeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (!(d is ICommandSource)) return; if (!(d is FrameworkElement || d is FrameworkContentElement)) return; if ((bool)e.NewValue) { HookCommandParameterChanged(d); } else { UnhookCommandParameterChanged(d); } UpdateCommandState(d); } private static PropertyDescriptor GetCommandParameterPropertyDescriptor(object source) { return TypeDescriptor.GetProperties(source.GetType())["CommandParameter"]; } private static void HookCommandParameterChanged(object source) { var propertyDescriptor = GetCommandParameterPropertyDescriptor(source); propertyDescriptor.AddValueChanged(source, OnCommandParameterChanged); // N.B. Using PropertyDescriptor.AddValueChanged will cause "source" to never be garbage collected, // so we need to hook the Unloaded event and call RemoveValueChanged there. HookUnloaded(source); } private static void UnhookCommandParameterChanged(object source) { var propertyDescriptor = GetCommandParameterPropertyDescriptor(source); propertyDescriptor.RemoveValueChanged(source, OnCommandParameterChanged); UnhookUnloaded(source); } private static void HookUnloaded(object source) { var fe = source as FrameworkElement; if (fe != null) { fe.Unloaded += OnUnloaded; } var fce = source as FrameworkContentElement; if (fce != null) { fce.Unloaded += OnUnloaded; } } private static void UnhookUnloaded(object source) { var fe = source as FrameworkElement; if (fe != null) { fe.Unloaded -= OnUnloaded; } var fce = source as FrameworkContentElement; if (fce != null) { fce.Unloaded -= OnUnloaded; } } static void OnUnloaded(object sender, RoutedEventArgs e) { UnhookCommandParameterChanged(sender); } static void OnCommandParameterChanged(object sender, EventArgs ea) { UpdateCommandState(sender); } private static void UpdateCommandState(object target) { var commandSource = target as ICommandSource; if (commandSource == null) return; var rc = commandSource.Command as RoutedCommand; if (rc != null) { CommandManager.InvalidateRequerySuggested(); } var dc = commandSource.Command as IDelegateCommand; if (dc != null) { dc.RaiseCanExecuteChanged(); } } } }
źródło
Istnieje stosunkowo prosty sposób „naprawienia” tego problemu za pomocą narzędzia DelegateCommand, chociaż wymaga on zaktualizowania źródła DelegateCommand i ponownej kompilacji Microsoft.Practices.Composite.Presentation.dll.
1) Pobierz kod źródłowy Prism 1.2 i otwórz plik CompositeApplicationLibrary_Desktop.sln. Tutaj znajduje się projekt Composite.Presentation.Desktop, który zawiera źródło DelegateCommand.
2) W ramach zdarzenia publicznego EventHandler CanExecuteChanged zmodyfikuj, aby brzmiało następująco:
3) W obszarze chronionego wirtualnego void OnCanExecuteChanged () zmodyfikuj go w następujący sposób:
4) Ponownie skompiluj rozwiązanie, a następnie przejdź do folderu Debug lub Release, w którym znajdują się skompilowane biblioteki DLL. Skopiuj Microsoft.Practices.Composite.Presentation.dll i .pdb (jeśli chcesz) do miejsca, w którym odwołujesz się do zestawów zewnętrznych, a następnie ponownie skompiluj aplikację, aby pobrać nowe wersje.
Następnie CanExecute powinno być uruchamiane za każdym razem, gdy interfejs użytkownika renderuje elementy powiązane z danym DelegateCommand.
Uważaj, Joe
refereejoe na gmail
źródło
Po przeczytaniu kilku dobrych odpowiedzi na podobne pytania, w twoim przykładzie nieznacznie zmieniłem DelegateCommand, aby działało. Zamiast używać:
Zmieniłem to na:
Usunąłem następujące dwie metody, ponieważ byłem zbyt leniwy, aby je naprawić
i
I to wszystko ... wydaje się, że zapewnia to wywołanie CanExecute, gdy Binding się zmieni i po metodzie Execute
Nie zostanie automatycznie wyzwolony, jeśli ViewModel zostanie zmieniony, ale jak wspomniano w tym wątku, jest to możliwe przez wywołanie polecenia CommandManager.InvalidateRequerySuggested w wątku GUI
źródło
DispatcherPriority.Normal
jest to zbyt wysokie, aby działać niezawodnie (lub w ogóle, w moim przypadku). UżywanieDispatcherPriority.Loaded
działa dobrze i wydaje się bardziej odpowiednie (tj. Wyraźnie wskazuje, że delegat nie ma być wywoływany, dopóki elementy interfejsu użytkownika skojarzone z modelem widoku nie zostaną w rzeczywistości załadowane).Hej Jonas, nie jestem pewien, czy to zadziała w szablonie danych, ale oto składnia powiązania, której używam w menu kontekstowym ListView, aby pobrać bieżący element jako parametr polecenia:
CommandParameter = "{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"
źródło
Zalogowałem to jako błąd w WPF w .Net 4.0, ponieważ problem nadal występuje w wersji Beta 2.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
źródło
Niektóre z tych odpowiedzi dotyczą powiązania z DataContext w celu pobrania samego polecenia, ale pytanie dotyczyło wartości parametru CommandParameter null, gdy nie powinno. My też tego doświadczyliśmy. W pewnym sensie znaleźliśmy bardzo prosty sposób, aby to zadziałało w naszym ViewModel. Dotyczy to w szczególności problemu zerowego parametru CommandParameter zgłoszonego przez klienta z jednym wierszem kodu. Zwróć uwagę na Dispatcher.BeginInvoke ().
public DelegateCommand<objectToBePassed> CommandShowReport { get { // create the command, or pass what is already created. var command = _commandShowReport ?? (_commandShowReport = new DelegateCommand<object>(OnCommandShowReport, OnCanCommandShowReport)); // For the item template, the OnCanCommand will first pass in null. This will tell the command to re-pass the command param to validate if it can execute. Dispatcher.BeginInvoke((Action) delegate { command.RaiseCanExecuteChanged(); }, DispatcherPriority.DataBind); return command; } }
źródło
To długa szansa. aby to debugować, możesz spróbować:
- sprawdzić zdarzenie PreviewCanExecute.
- użyj snoop / wpf mole, aby zajrzeć do środka i zobaczyć, jaki jest parametr polecenia.
HTH,
źródło
U mnie działa również commandManager.InvalidateRequerySuggested. Myślę, że poniższy link mówi o podobnym problemie, a M $ dev potwierdził ograniczenie w aktualnej wersji, a commandManager.InvalidateRequerySuggested stanowi obejście tego problemu. http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/
Ważny jest czas wywołania polecenia commandManager.InvalidateRequerySuggested. Powinno to zostać wywołane po powiadomieniu o odpowiedniej zmianie wartości.
źródło
Oprócz sugestii Eda Ball'a dotyczącej ustawienia CommandParameter przed Command , upewnij się, że metoda CanExecute ma parametr typu obiektu .
Mam nadzieję, że uniemożliwi to komuś spędzenie ogromnej ilości czasu, aby dowiedzieć się, jak odebrać SelectedItems jako parametr CanExecute
źródło