Saya mengalami masalah dengan WPF dan Perintah yang terikat ke Tombol di dalam DataTemplate dari ItemsControl. Skenarionya cukup lurus ke depan. ItemsControl terikat ke daftar objek, dan saya ingin dapat menghapus setiap objek dalam daftar dengan mengklik sebuah Tombol. Tombol menjalankan Perintah, dan Perintah menangani penghapusan. CommandParameter terikat ke Objek yang ingin saya hapus. Dengan cara itu saya tahu apa yang diklik pengguna. Seorang pengguna seharusnya hanya dapat menghapus objek "sendiri" mereka - jadi saya perlu melakukan beberapa pemeriksaan dalam panggilan "CanExecute" dari Perintah untuk memverifikasi bahwa pengguna memiliki izin yang tepat.
Masalahnya adalah bahwa parameter yang diteruskan ke CanExecute adalah NULL saat pertama kali dipanggil - jadi saya tidak dapat menjalankan logika untuk mengaktifkan / menonaktifkan perintah. Namun, jika saya membuatnya selalu diaktifkan, lalu mengklik tombol untuk menjalankan perintah, CommandParameter akan diteruskan dengan benar. Jadi itu berarti pengikatan terhadap CommandParameter berfungsi.
XAML untuk ItemsControl dan DataTemplate terlihat seperti ini:
<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>
Jadi seperti yang Anda lihat, saya memiliki daftar objek Komentar. Saya ingin CommandParameter dari DeleteCommentCommand terikat ke objek Command.
Jadi saya kira pertanyaan saya adalah: apakah ada yang pernah mengalami masalah ini sebelumnya? CanExecute dipanggil di Command saya, tetapi parameternya selalu NULL saat pertama kali - mengapa demikian?
Pembaruan: Saya dapat mempersempit masalah sedikit. Saya menambahkan Debug ValueConverter kosong sehingga saya bisa mengeluarkan pesan ketika CommandParameter terikat data. Ternyata masalahnya adalah metode CanExecute dijalankan sebelum CommandParameter terikat ke tombol. Saya telah mencoba mengatur CommandParameter sebelum Command (seperti yang disarankan) - tetapi masih tidak berhasil. Ada tips tentang cara mengontrolnya.
Pembaruan2: Apakah ada cara untuk mendeteksi kapan pengikatan "selesai", sehingga saya dapat memaksa evaluasi ulang perintah? Juga - apakah masalah saya memiliki beberapa Tombol (satu untuk setiap item di ItemsControl) yang terikat ke contoh yang sama dari objek-Perintah?
Pembaruan3: Saya telah mengunggah reproduksi bug ke SkyDrive saya: http://cid-1a08c11c407c0d8e.skydrive.live.com/self.aspx/Code%20samples/CommandParameterBinding.zip
Jawaban:
Saya menemukan masalah serupa dan menyelesaikannya menggunakan TriggerConverter saya yang terpercaya.
Pengonversi nilai ini mengambil sejumlah parameter dan meneruskan yang pertama kembali sebagai nilai yang dikonversi. Saat digunakan dalam MultiBinding dalam kasus Anda, tampilannya akan seperti berikut ini.
<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>
Anda harus menambahkan TriggerConverter sebagai sumber daya di suatu tempat agar ini berfungsi. Sekarang properti Command disetel sebelum nilai untuk CommandParameter tersedia. Anda bahkan dapat mengikat ke RelativeSource.Self dan CommandParameter, bukan. untuk mencapai efek yang sama.
sumber
Saya mengalami masalah yang sama saat mencoba mengikat perintah pada model tampilan saya.
Saya mengubahnya untuk menggunakan pengikatan sumber relatif daripada merujuk ke elemen dengan nama dan itu berhasil. Pengikatan parameter tidak berubah.
Kode Lama:
Kode Baru:
Pembaruan : Saya baru saja menemukan masalah ini tanpa menggunakan ElementName, saya mengikat perintah pada model tampilan saya dan konteks data tombol adalah model tampilan saya. Dalam hal ini saya hanya perlu memindahkan atribut CommandParameter sebelum atribut Command di deklarasi Tombol (di XAML).
sumber
CommandParameter
danCommand
membuatku takut.Saya telah menemukan bahwa urutan saya mengatur Command dan CommandParameter membuat perbedaan. Menyetel properti Command menyebabkan CanExecute dipanggil segera, jadi Anda ingin CommandParameter sudah disetel pada saat itu.
Saya telah menemukan bahwa mengubah urutan properti di XAML sebenarnya dapat berpengaruh, meskipun saya tidak yakin hal itu akan menyelesaikan masalah Anda. Ini patut dicoba.
Anda sepertinya menyarankan bahwa tombol tidak pernah diaktifkan, yang mengejutkan, karena saya mengharapkan CommandParameter disetel segera setelah properti Command dalam contoh Anda. Apakah memanggil CommandManager.InvalidateRequerySuggested () menyebabkan tombol menjadi aktif?
sumber
Saya telah menemukan opsi lain untuk mengatasi masalah ini yang ingin saya bagikan. Karena metode perintah CanExecute dieksekusi sebelum properti CommandParameter disetel, saya membuat kelas pembantu dengan properti terlampir yang memaksa metode CanExecute dipanggil lagi ketika pengikatan berubah.
Dan kemudian pada tombol Anda ingin mengikat parameter perintah ke ...
<Button Content="Press Me" Command="{Binding}" helpers:ButtonHelper.CommandParameter="{Binding MyParameter}" />
Saya harap ini dapat membantu orang lain dengan masalah ini.
sumber
Ini adalah utas lama, tetapi karena Google membawa saya ke sini ketika saya mengalami masalah ini, saya akan menambahkan apa yang berhasil untuk saya untuk DataGridTemplateColumn dengan sebuah tombol.
Ubah pengikatan dari:
untuk
Tidak yakin mengapa ini berhasil, tetapi berhasil untuk saya.
sumber
Saya baru-baru ini menemukan masalah yang sama (bagi saya itu untuk item menu dalam menu konteks), nad meskipun itu mungkin bukan solusi yang cocok untuk setiap situasi, saya menemukan cara yang berbeda (dan jauh lebih pendek!) Untuk menyelesaikannya masalah:
<MenuItem Header="Open file" Command="{Binding Tag.CommandOpenFile, IsAsync=True, RelativeSource={RelativeSource AncestorType={x:Type ContextMenu}}}" CommandParameter="{Binding Name}" />
Mengabaikan
Tag
solusi berbasis untuk kasus khusus menu konteks, kuncinya di sini adalah mengikatCommandParameter
secara teratur, tetapi mengikatCommand
dengan tambahanIsAsync=True
. Ini akan sedikit menunda pengikatan perintah aktual (dan oleh karena ituCanExecute
panggilannya), sehingga parameter sudah tersedia. Ini berarti, meskipun, untuk sesaat, status-aktif mungkin salah, tetapi untuk kasus saya, itu bisa diterima.sumber
Anda mungkin dapat menggunakan milik saya
CommandParameterBehavior
yang saya posting ke forum Prism kemarin. Ini menambahkan perilaku yang hilang di mana perubahanCommandParameter
penyebabCommand
harus dipertanyakan kembali.Ada beberapa kerumitan di sini yang disebabkan oleh upaya saya untuk menghindari kebocoran memori yang disebabkan jika Anda menelepon
PropertyDescriptor.AddValueChanged
tanpa menelepon nantiPropertyDescriptor.RemoveValueChanged
. Saya mencoba dan memperbaikinya dengan membatalkan registrasi pawang saat ekement dibongkar.Anda mungkin perlu menghapus
IDelegateCommand
barang - barang tersebut kecuali jika Anda menggunakan Prism (dan ingin membuat perubahan yang sama seperti saya pada perpustakaan Prism). Juga perhatikan bahwa kami umumnya tidak menggunakanRoutedCommand
s di sini (kami menggunakan PrismDelegateCommand<T>
untuk hampir semua hal) jadi tolong jangan anggap saya bertanggung jawab jika panggilan saya untukCommandManager.InvalidateRequerySuggested
memicu semacam kaskade runtuhnya fungsi gelombang kuantum yang menghancurkan alam semesta yang diketahui atau apa pun.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(); } } } }
sumber
Ada cara yang relatif sederhana untuk "memperbaiki" masalah ini dengan DelegateCommand, meskipun itu memerlukan pembaruan sumber DelegateCommand dan kompilasi ulang Microsoft.Practices.Composite.Presentation.dll.
1) Unduh kode sumber Prism 1.2 dan buka CompositeApplicationLibrary_Desktop.sln. Di sini adalah proyek Composite.Presentation.Desktop yang berisi sumber DelegateCommand.
2) Di bawah acara publik EventHandler CanExecuteChanged, ubah untuk membaca sebagai berikut:
3) Di bawah virtual void OnCanExecuteChanged () yang dilindungi, ubah sebagai berikut:
4) Kompilasi ulang solusi, lalu navigasikan ke folder Debug atau Rilis tempat DLL yang dikompilasi berada. Salin Microsoft.Practices.Composite.Presentation.dll dan .pdb (jika Anda ingin) ke tempat Anda mereferensikan rakitan eksternal Anda, dan kemudian kompilasi ulang aplikasi Anda untuk menarik versi baru.
Setelah ini, CanExecute harus diaktifkan setiap kali UI merender elemen yang terikat ke DelegateCommand yang dimaksud.
Jaga dirimu, Joe
refereejoe di gmail
sumber
Setelah membaca beberapa jawaban bagus untuk pertanyaan serupa, saya mengubah sedikit Perintah Delegasi dalam contoh Anda untuk membuatnya berfungsi. Daripada menggunakan:
Saya mengubahnya menjadi:
Saya menghapus dua metode berikut karena saya terlalu malas untuk memperbaikinya
dan
Dan itu saja ... ini tampaknya memastikan bahwa CanExecute akan dipanggil saat Binding berubah dan setelah metode Execute
Ini tidak akan secara otomatis memicu jika ViewModel diubah tetapi seperti yang disebutkan di utas ini dimungkinkan dengan memanggil CommandManager.InvalidateRequerySuggested di utas GUI
sumber
DispatcherPriority.Normal
terlalu tinggi untuk bekerja dengan andal (atau sama sekali, dalam kasus saya). PenggunaanDispatcherPriority.Loaded
berfungsi dengan baik, dan tampaknya lebih sesuai (yaitu secara eksplisit menunjukkan bahwa delegasi tidak akan dipanggil hingga elemen UI yang terkait dengan model tampilan sebenarnya telah dimuat).Hai Jonas, tidak yakin apakah ini akan berfungsi di templat data, tetapi berikut adalah sintaks pengikatan yang saya gunakan dalam menu Konteks ListView untuk mengambil item saat ini sebagai parameter perintah:
CommandParameter = "{Binding RelativeSource = {RelativeSource AncestorType = ContextMenu}, Path = PlacementTarget.SelectedItem, Mode = TwoWay}"
sumber
Saya telah mencatat ini sebagai bug terhadap WPF di .Net 4.0, karena masalah masih ada di Beta 2.
https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=504976
sumber
Beberapa dari jawaban ini adalah tentang mengikat ke DataContext untuk mendapatkan Perintah itu sendiri, tetapi pertanyaannya adalah tentang CommandParameter menjadi nol padahal seharusnya tidak. Kami juga mengalami ini. Berdasarkan firasat, kami menemukan cara yang sangat sederhana untuk membuatnya berfungsi di ViewModel kami. Ini khusus untuk masalah null CommandParameter yang dilaporkan oleh pelanggan, dengan satu baris kode. Perhatikan 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; } }
sumber
Ini tembakan yang jauh. untuk men-debug ini, Anda dapat mencoba:
- memeriksa acara PreviewCanExecute.
- gunakan snoop / wpf mol untuk mengintip ke dalam dan melihat parameter perintahnya.
HTH,
sumber
CommandManager.InvalidateRequerySuggested juga berfungsi untuk saya. Saya yakin tautan berikut berbicara tentang masalah serupa, dan M $ dev mengkonfirmasi batasan dalam versi saat ini, dan commandManager.InvalidateRequerySuggested adalah solusinya.http://social.expression.microsoft.com/Forums/en-US/wpf/thread/c45d2272-e8ba-4219-bb41-1e5eaed08a1f/
Yang penting adalah waktu pemanggilan commandManager.InvalidateRequerySuggested. Ini harus dipanggil setelah perubahan nilai yang relevan diberitahukan.
sumber
Di samping saran Ed Ball tentang menyetel CommandParameter sebelum Command , pastikan metode CanExecute Anda memiliki parameter tipe objek .
Semoga ini mencegah seseorang menghabiskan banyak waktu yang saya lakukan untuk mencari tahu cara menerima SelectedItems sebagai parameter CanExecute
sumber