RadGridView Row fails to find ancestor on some cases

4 posts, 1 answers
  1. BENN
    BENN avatar
    59 posts
    Member since:
    Dec 2011

    Posted 09 Dec 2015 Link to this post

    This is related to the grid's virtualization. The following code demonstrates the problem

    View Models and models:

    public class MainViewModel : ViewModelBase
    {
        private ObservableCollection<Person> _persons = new ObservableCollection<Person>();
        private DelegateCommand<Person> _deletePersonCommand;
     
        public MainViewModel()
        {
            for (int i = 0; i < 30; i++)
            {
                _persons.Add(new Person()
                {
                    FirstName = "Hello" + i,
                    LastName = "World" + i,
                });
            }
        }
     
        public ObservableCollection<Person> Persons
        {
            get
            {
                return _persons;
            }
        }
     
        public DelegateCommand<Person> DeletePersonCommand
        {
            get
            {
                if (_deletePersonCommand == null)
                {
                    _deletePersonCommand = new DelegateCommand<Person>(deletePerson);
                }
                return _deletePersonCommand;
            }
        }
     
        private void deletePerson(Person person)
        {
            _persons.Remove(person);
        }
    }
     
    public class Person
    {
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }

     

     I have excluded the definition of the Delegate Command. It doesn't matter to this problem, since it will also happen with CallMethodAction, for example...

    Here is the XAML:

    <Window x:Class="GridViewRowRelativeSourceBug.MainWindow"
            Title="MainWindow" Height="250" Width="525">
        <Grid>
            <telerik:RadGridView Name="radGridView1" ShowGroupPanel="False" ShowGroupFooters="False" AutoGenerateColumns="False" ItemsSource="{Binding Persons}">
                <telerik:RadGridView.Columns>
                    <telerik:GridViewDataColumn IsReadOnly="True" Header="First Name">
                        <telerik:GridViewDataColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding FirstName}" />
                            </DataTemplate>
                        </telerik:GridViewDataColumn.CellTemplate>
                    </telerik:GridViewDataColumn>
                     
                    <telerik:GridViewDataColumn IsReadOnly="True" Header="Last Name">
                        <telerik:GridViewDataColumn.CellTemplate>
                            <DataTemplate>
                                <TextBlock Text="{Binding LastName}" />
                            </DataTemplate>
                        </telerik:GridViewDataColumn.CellTemplate>
                    </telerik:GridViewDataColumn>
     
                    <telerik:GridViewDataColumn IsReadOnly="True" Header="Delete" Width="80">
                        <telerik:GridViewDataColumn.CellTemplate>
                            <DataTemplate>
                                <Button Content="Delete Me" Command="{Binding RelativeSource={RelativeSource AncestorType=telerik:RadGridView}, Path=DataContext.DeletePersonCommand}" CommandParameter="{Binding}" />
                            </DataTemplate>
                        </telerik:GridViewDataColumn.CellTemplate>
                    </telerik:GridViewDataColumn>
                </telerik:RadGridView.Columns>
            </telerik:RadGridView>
        </Grid>
    </Window>

     

    Please note that instead of having a command for each item, the mainViewModel that contains the items, gets the command and the item to remove as a command parameter.

     

    If you run this example, then there are 2 ways to delete the rows, one will work and the other will fail after deleting about 10 rows

     

    Way #1, deleting from the start

    first delete the items from the start, meaning, click on the "Delete Me" button of the first row, now after the first row was deleted, then click the "Delete Me" of the new first row.

    You should be able to delete all the rows in that grid.

     

     

    Way #2, deleting from the end:

    Scroll to the end of the grid, and click on the "Delete Me" of the last row. Once it was removed, repeat this procedure for about 10 more times (the number of times is related to the number if rows that fits into the view, and I assume that container recycling is one of the causes to this problem).

    You should see that at some point, the "Delete Me" will not do anything, and that the deletePerson function is never called.
    In the Output window of Visual Studio you will see:

    System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='Telerik.Windows.Controls.RadGridView', AncestorLevel='1''. BindingExpression:Path=DataContext.DeletePersonCommand; DataItem=null; target element is 'Button' (Name=''); target property is 'Command' (type 'ICommand')

     

     

    I must say that this bug is pretty old, and its weird that no one has reported it yet.

     

     

  2. Stefan Nenchev
    Admin
    Stefan Nenchev avatar
    280 posts

    Posted 14 Dec 2015 Link to this post

    Hello Benn, 

    Indeed, this appears to be related to RadGridView's virtualization and we managed to replicate it. We'll further investigate the precise reason and whether this can be resolved without intervening with the control's functionality. Meanwhile. I'd suggest adding the main view model as a static resource to the view and binding the command through it in order to avoid the issue.

    Regards,
    Stefan Nenchev
    Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  3. UI for WPF is Visual Studio 2017 Ready
  4. BENN
    BENN avatar
    59 posts
    Member since:
    Dec 2011

    Posted 15 Dec 2015 Link to this post

    Hi, there is lots of situations where the VM can't be a static one.

    I have implemented a solution specifically for commands using a solution I have found on the net.

     

    public class CommandReference : Freezable, ICommand
        {
            public event EventHandler CanExecuteChanged;
            public event EventHandler CommandExecuted;
            public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandReference), new PropertyMetadata(new PropertyChangedCallback(OnCommandChanged)));
             
            public ICommand Command
            {
                get { return (ICommand)GetValue(CommandProperty); }
                set { SetValue(CommandProperty, value); }
            }
     
            #region ICommand Members
            public bool CanExecute(object parameter)
            {
                return (Command != null) ? Command.CanExecute(parameter) : false;
            }
     
            public void Execute(object parameter)
            {
                Command.Execute(parameter);
              
                if (CommandExecuted != null)
                    CommandExecuted(this, null);
            }
     
     
            private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                CommandReference commandReference = d as CommandReference;
                if (commandReference != null)
                {
                    ICommand oldCommand = e.OldValue as ICommand;
                    if (oldCommand != null) oldCommand.CanExecuteChanged -= commandReference.CanExecuteChanged;
     
                    ICommand newCommand = e.NewValue as ICommand;
                    if (newCommand != null) newCommand.CanExecuteChanged += commandReference.CanExecuteChanged;
                }
            }
            #endregion
     
            #region Freezable
            protected override Freezable CreateInstanceCore()
            {
                return new CommandReference();
            }
            #endregion
        }

     

     And then in the XAML:

     

    <UserControl.Resources>
         <commands:CommandReference Command="{Binding DeletePersonCommand}" x:Key="deletePersonCommand"/>
    </UserControl.Resources>

     

     

    And then reference the static command from the row (So instead of having a static resource VM, we have a static resource command), and the command parameter can be passed just like before.

    Ugly, but it works

     

     

     

  5. Answer
    Petya
    Admin
    Petya avatar
    975 posts

    Posted 15 Dec 2015 Link to this post

    Hello Benn,

    You certainly have a point about the view models being static and when further discussing this within the team we agreed there are other cases where such binding would be the desired approach. As at this point we couldn't engage with a specific time-frame for a fix, I'm glad to hear you managed to find a workaround that works for you. You can also subscribe to this item on the public portal to receive updates about this issue.

    Regards,
    Petya
    Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Back to Top