Klaus Nji
Klaus Nji

Reputation: 18857

wpf ICommand binding in data grid records causing memory leak

Let's say I have a property on a class that needs to be rendered using a Hyperlink control. The hyperlink is to be bound to a command on the view model such that clicking on it triggers some action. Something like this:

<Style x:Key="HyperlinkStyle" TargetType="{x:Type igDP:CellValuePresenter}">
   <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type igDP:CellValuePresenter}">
                    <Border BorderBrush="{StaticResource DataGridCellOuterBorder}" BorderThickness="1,0,0,0"  >
                        <TextBlock Margin="5">
                                <Hyperlink                                      
                                    Command="{Binding ElementName=dataGrid, Path=DataContext.Commands[GetSolutionSummaryCmd], Mode=OneTime}"
                                    CommandParameter="{Binding Path=(igDP:DataRecord.DataItem), Mode=OneTime}">
                                    <TextBlock Text="{TemplateBinding Value}"/>                                                                      
                                </Hyperlink>                                                                                
                            </TextBlock>
                    </Border>                 
                </ControlTemplate>
            </Setter.Value>
        </Setter>       
    </Style>

How do I ensure that when data items are removed from the grid, the binding between dataGrid.DataContext.Command[GetSolutionSummaryCmd], an implementation of ICommand, and each data record with a hyperlink column is destroyed, so data item can be garbage collected? Otherwise, I see a potential for memory leaking here.

Also, GetSolutionSummaryCmd is an instance of RelayCommand implemented like this:

public class RelayCommand  : ICommand
{
    readonly protected Predicate<object> _canExecute;
    readonly protected Action<object> _execute;

    public RelayCommand(Predicate<object> canExecute, Action<object> execute)
        : this(canExecute, execute, true)
    {
    }

    public RelayCommand(Predicate<object> canExecute, Action<object> execute, bool isCommandAllowed)
    {
        _canExecute = canExecute;
        _execute = execute;
        IsAllowed = isCommandAllowed;
    }

    public void RaiseCanExecuteChanged()
    {
        if (this.CanExecuteChanged != null)
            this.CanExecuteChanged(this, EventArgs.Empty);
    }

    #region ICommand Members

    public virtual bool CanExecute(object parameter)
    {
        return _canExecute(parameter);
    }

    public event EventHandler CanExecuteChanged;

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

    #endregion 
}

In contrast to this, I need to be able to raise CanExecuteChanged.

Upvotes: 2

Views: 716

Answers (1)

Klaus Nji
Klaus Nji

Reputation: 18857

I used the suggestions here to implement a solution for this problem. Leak was definitely being caused by hyperlink elements used in the above Style. ANTS profiler indicated this with a positive instance count for System.Windows.EffectiveValueEntry[] between memory snapshots. And when I look at the object reference graph for this class, there was always a reference to a hyperlink instance within the chain.

Underlying data was changed so that hyperlinks can always be executed when clicked. This means that CanExecuteChanged event of ICommand does not have to be raised allowing me to define some kind of NoReferenceRelayCommand class like this:

    public class NoReferenceRelayCommand : ICommand
    {
        protected readonly Action<object> _execute;

        public NoReferenceRelayCommand(Action<object> execute)
        {
            Guard.ThrowIfArgumentIsNull(execute);
            _execute = execute;
        }

        #region ICommand Members

        public bool CanExecute(object parameter)
        {
            return true;
        }

        public event EventHandler CanExecuteChanged
        {
            add { }
            remove { }
        }

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

        #endregion
    }

Profiling solution over 10 hours shows that instance number of System.Windows.EffectiveValueEntry does not increase.

Upvotes: 2

Related Questions