2012-04-23 11 views
6

मैं समझने की कोशिश कर रहा हूं कि यूआई से हटाए गए कमांड स्रोत पर CanExecute क्यों लागू किया गया है। यहाँ यह दर्शाने के एक सरल कार्यक्रम है:यूआई से कमांड स्रोत हटा दिए जाने के बाद CanExecute क्यों लागू किया जाता है?

<Window x:Class="WpfApplication1.MainWindow" 
     xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
     Height="350" Width="525"> 
    <StackPanel> 
     <ListBox ItemsSource="{Binding Items}"> 
      <ListBox.ItemTemplate> 
       <DataTemplate> 
        <StackPanel> 
         <Button Content="{Binding Txt}" 
           Command="{Binding Act}" /> 
        </StackPanel> 
       </DataTemplate> 
      </ListBox.ItemTemplate> 
     </ListBox> 
     <Button Content="Remove first item" Click="Button_Click" /> 
    </StackPanel> 
</Window> 

कोड-पीछे:

Removed item marked 'Removed' 
Item removed 
Why is this happening? 
Why is this happening? 
:

public partial class MainWindow : Window 
{ 
    public class Foo 
    { 
     static int _seq = 0; 
     int _txt = _seq++; 
     RelayCommand _act; 
     public bool Removed = false; 

     public string Txt { get { return _txt.ToString(); } } 

     public ICommand Act 
     { 
      get 
      { 
       if (_act == null) { 
        _act = new RelayCommand(
         param => { }, 
         param => { 
          if (Removed) 
           Console.WriteLine("Why is this happening?"); 
          return true; 
         }); 
       } 
       return _act; 
      } 
     } 
    } 

    public ObservableCollection<Foo> Items { get; set; } 

    public MainWindow() 
    { 
     Items = new ObservableCollection<Foo>(); 
     Items.Add(new Foo()); 
     Items.Add(new Foo()); 
     Items.CollectionChanged += 
      new NotifyCollectionChangedEventHandler(Items_CollectionChanged); 
     DataContext = this; 
     InitializeComponent(); 
    } 

    void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) 
    { 
     if (e.Action == NotifyCollectionChangedAction.Remove) 
      foreach (Foo foo in e.OldItems) { 
       foo.Removed = true; 
       Console.WriteLine("Removed item marked 'Removed'"); 
      } 
    } 

    void Button_Click(object sender, RoutedEventArgs e) 
    { 
     Items.RemoveAt(0); 
     Console.WriteLine("Item removed"); 
    } 
} 

जब मैं बटन एक बार "पहला आइटम निकालें" पर क्लिक करें, मैं इस आउटपुट प्राप्त

"यह क्यों हो रहा है?" प्रत्येक बार जब मैं खिड़की के कुछ खाली हिस्से पर क्लिक करता हूं तो मुद्रित रहता रहता है।

ऐसा क्यों हो रहा है? और हटाए गए कमांड स्रोतों पर CanExecute को लागू होने से रोकने के लिए मुझे क्या करना चाहिए या क्या करना चाहिए?

नोट: रिलेकॉमैंड here पाया जा सकता है।

माइकल Edenfield प्रश्नों के उत्तर:

Q1: जब CanExecute हटा बटन पर शुरू हो जाती है की Callstack:

WpfApplication1.exe WpfApplication1.MainWindow.Foo.get_Act.AnonymousMethod__1 (ऑब्जेक्ट पैरा) लाइन 30 WpfApplication1.exe! WpfApplication1.RelayCommand.CanExecute (ऑब्जेक्ट पैरामीटर) लाइन 41 + 0x1a बाइट्स प्रस्तुति Framework.dll! MS.Internal.Commands.CommandHelpers.CanExecuteCommandSource (Syste m.Windows.Input.ICommandSource कमांडसोर्स) + 0x8a बाइट्स प्रेजेंटेशनफ्रेमवर्क.dll! System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute() + 0x18 बाइट्स प्रस्तुति Framework.dll! System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged (ऑब्जेक्ट प्रेषक, System.EventArgs ई) + 0x5 बाइट प्रस्तुति Core.dll! System.Windows.Input.CommandManager.CallWeakReferenceHandlers (System.Collections.Generic.List हैंडलर) + 0xac बाइट प्रस्तुति Core.dll! System.Windows.Input.CommandManager। RaiseRequerySuggested (वस्तु obj) + 0xf बाइट्स

Q2: (? सिर्फ पहला नहीं) इसके अलावा, यह हो रहा रखता है, तो आप सूची से बटन के सभी हटाने

हां।

+0

मुझे रिलेकॉमैंड याद आती है। यह क्या है? – Gqqnbig

+0

मैंने रिलेकॉमैंड के कार्यान्वयन के लिए एक लिंक जोड़ा। –

+0

क्या आपने ईवेंट के दौरान कॉलस्टैक की जांच करने की कोशिश की है और यह देखने के लिए कि क्या ट्रिगर हुआ? साथ ही, क्या यह तब भी होता है जब आप सूची के सभी * बटनों को हटाते हैं (केवल पहले नहीं?) –

उत्तर

2

मुद्दा यह है कि आदेश स्रोत (अर्थात बटन) आदेश में यह, के लिए बाध्य है, ताकि जब भी CommandManager.RequerySuggested आग, CanExecute आग के साथ-साथ, लंबे समय के बाद आदेश स्रोत चला गया है की CanExecuteChanged की सदस्यता नहीं त्यागी करता है।

इस मैं RelayCommand पर IDisposable लागू किया है, और इसलिए है कि जब भी एक मॉडल वस्तु निकाल दिया जाता है आवश्यक कोड जोड़ा हल करने के लिए, और इसलिए यूआई, निपटान (से निकाल दिया जाता) अपने सभी RelayCommand पर शुरू हो जाती है।

यह संशोधित किया गया है RelayCommand (मूल here है):

public class RelayCommand : ICommand, IDisposable 
{ 
    #region Fields 

    List<EventHandler> _canExecuteSubscribers = new List<EventHandler>(); 
    readonly Action<object> _execute; 
    readonly Predicate<object> _canExecute; 

    #endregion // Fields 

    #region Constructors 

    public RelayCommand(Action<object> execute) 
     : this(execute, null) 
    { 
    } 

    public RelayCommand(Action<object> execute, Predicate<object> canExecute) 
    { 
     if (execute == null) 
      throw new ArgumentNullException("execute"); 

     _execute = execute; 
     _canExecute = canExecute; 
    } 

    #endregion // Constructors 

    #region ICommand 

    [DebuggerStepThrough] 
    public bool CanExecute(object parameter) 
    { 
     return _canExecute == null ? true : _canExecute(parameter); 
    } 

    public event EventHandler CanExecuteChanged 
    { 
     add 
     { 
      CommandManager.RequerySuggested += value; 
      _canExecuteSubscribers.Add(value); 
     } 
     remove 
     { 
      CommandManager.RequerySuggested -= value; 
      _canExecuteSubscribers.Remove(value); 
     } 
    } 

    public void Execute(object parameter) 
    { 
     _execute(parameter); 
    } 

    #endregion // ICommand 

    #region IDisposable 

    public void Dispose() 
    { 
     _canExecuteSubscribers.ForEach(h => CanExecuteChanged -= h); 
     _canExecuteSubscribers.Clear(); 
    } 

    #endregion // IDisposable 
} 

जहाँ भी मैं ऊपर का उपयोग करें, मैं ट्रैक सभी instantiated RelayCommands तो मैं Dispose() आह्वान कर सकते हैं जब समय आता है:

Dictionary<string, RelayCommand> _relayCommands 
    = new Dictionary<string, RelayCommand>(); 

public ICommand SomeCmd 
{ 
    get 
    { 
     RelayCommand command; 
     string commandName = "SomeCmd"; 
     if (_relayCommands.TryGetValue(commandName, out command)) 
      return command; 
     command = new RelayCommand(
      param => {}, 
      param => true); 
     return _relayCommands[commandName] = command; 
    } 
} 

void Dispose() 
{ 
    foreach (string commandName in _relayCommands.Keys) 
     _relayCommands[commandName].Dispose(); 
    _relayCommands.Clear(); 
} 
0

लैम्बडा अभिव्यक्ति और घटनाओं का उपयोग करने के साथ एक ज्ञात समस्या है जो आप ट्रिगर करने लगते हैं। मुझे इसे "बग" कहने में संकोच नहीं है क्योंकि मुझे यह जानने के लिए पर्याप्त आंतरिक विवरण नहीं समझते हैं कि यह इरादा व्यवहार है, लेकिन यह निश्चित रूप से मेरे प्रति अंतर्ज्ञानी लगता है।

यहाँ कुंजी संकेत अपने कॉल स्टैक के इस भाग है:

PresentationCore.dll!System.Windows.Input.CommandManager.CallWeakReferenceHandlers(
    System.Collections.Generic.List handlers) + 0xac bytes 

"कमजोर" घटनाओं की घटनाओं है कि लक्ष्य वस्तु को जीवित रखने के नहीं है ऊपर हुक करने के लिए एक तरीका है; इसका उपयोग यहां किया जा रहा है क्योंकि आप घटना हैंडलर के रूप में एक लांबा अभिव्यक्ति में गुजर रहे हैं, इसलिए "ऑब्जेक्ट" जिसमें विधि है आंतरिक रूप से जेनरेट की गई अज्ञात वस्तु है।समस्या यह है कि आपके ईवेंट के लिए add हैंडलर में ऑब्जेक्ट को पारित किया जा रहा है, एक अभिव्यक्ति का एक ही उदाहरण नहीं है क्योंकि इसे remove ईवेंट में पास किया जा रहा है, यह केवल एक कार्यात्मक समान वस्तु है, इसलिए यह आपके ईवेंट से सदस्यता नहीं ले रहा है ।

कई संभावित हल निम्न प्रश्नों में वर्णित है, के होते हैं:

Weak event handler model for use with lambdas

UnHooking Events with Lambdas in C#

Can using lambdas as event handlers cause a memory leak?

अपने मामले के लिए सबसे आसान अपने CanExecute ले जाने और वास्तविक में कोड निष्पादित करने के लिए है विधियां:

if (_act == null) { 
    _act = new RelayCommand(this.DoCommand, this.CanDoCommand); 
} 

private void DoCommand(object parameter) 
{ 
} 

private bool CanDoCommand(object parameter) 
{ 
    if (Removed) 
     Console.WriteLine("Why is this happening?"); 
    return true; 
} 

वैकल्पिक रूप से, यदि आप अपने ऑब्जेक्ट को Action<> और Func<> लेम्बास से एक बार प्रतिनिधि बनाने के लिए व्यवस्थित कर सकते हैं, उन्हें चर में स्टोर करें, और RelayCommand बनाते समय उन लोगों का उपयोग करें, यह उसी उदाहरण को उपयोग करने के लिए मजबूर करेगा। आईएमओ, आपके मामले के लिए जो शायद इसकी तुलना में अधिक जटिल है।

+0

गैर-अज्ञात विधियों को बनाना और उन्हें रिलेकॉमैंड के निर्माता के पैरामीटर के रूप में पास करना कुछ भी नहीं बदलता है। मैंने कुछ शोध किया है और ऐसा लगता है कि मुद्दा यह है कि कमांड स्रोत (बटन) अभी भी CanExecuteChanged की सदस्यता लेता है (यानी बटन स्वचालित रूप से ईवेंट पर हुक करता है, लेकिन सदस्यता समाप्त नहीं करता है। –