2

I am new to WPF development yet. I just quickly created a sample to recreate the scenario. I am switching the IsReadonly Property of the datagrid in rowdetailsview based on the checkbox in the parent row.

Everything works fine except in one particular scenario.

How to recreate problem.

Keep the initially create parent row checked. uncheck the parent row. go to the child row id property. clean everything in id field and tab out of that cell and you will see null reference exception.

I have no clue how to fix this issue. Any insight will be really useful.

code behind code:

 namespace WpfApplication7
{/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{

    public PerColl People { get; private set; }
    public MainWindow()
    {
        InitializeComponent();

        this.People = new PerColl();
        this.DataContext = this;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {

    }

    public class Person
   : INotifyPropertyChanged, IEditableObject
    {
        public string Name { get; set; }

        public double Salary
        {
            get { return _salary; }
            set
            {
                if (this._salary == value)
                    return;
                this._salary = value;
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Salary"));
            }

        }

        public bool IsLocked
        {
            get { return _isLocked; }
            set
            {

                if (this._isLocked == value)
                    return;
                this._isLocked = value;
                if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("IsLocked"));
            }
        }

        public ObservableCollection<Kid> Kids { get; set; }

        public Person(string name, double salary)
        {
            this.Name = name;
            this.Salary = salary;
        }


        public Person()
        {
            this.Salary = 10000;
            this.Name = "abc";
            this.IsLocked = true;
            this.Kids = new ObservableCollection<Kid>();
            this.Kids.Add(new Kid(1));
            this.Kids.Add(new Kid(2));
        }

        private bool _isLocked;
        private double _salary;
        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        public void BeginEdit()
        {
            if (isEdit) return;
            isEdit = true;
            this.backup = this.MemberwiseClone() as Person;
        }


        public void CancelEdit()
        {
            if (!this.isEdit)
                return;
            isEdit = false;
            this.Name = this.backup.Name;
            this.Salary = this.backup.Salary;
        }

        public void EndEdit()
        {
            if (this.isEdit == false)
                return;
            this.isEdit = false;
            this.backup = null;
        }



        private bool isEdit;
        private Person backup;
    }


    public class PerColl : ObservableCollection<Person> { }

    public class Kid : IEditableObject,INotifyPropertyChanged
    {
        public int Id
        {
            get { return _id; }
            set
            {
                this._id = value;
                if (PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Id"));

            }

        }
        public string Name
        {
            get { return _name; }
            set
            {
                this._name = value;
                if (PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));

            }
        }
        public Person Parent { get; set; }

        public Kid()
        {
            this.Id = 12345;
            this.Name = "kidname";
        }

        public Kid(int id, string name = "kidname")
        {
            this.Id = 12345;
            this.Name = name;
        }

        #region IEditableObject Members

        public void BeginEdit()
        {
            if (isEdit) return;
            isEdit = true;
            this.backup = this.MemberwiseClone() as Kid;
        }

        public void CancelEdit()
        {
            if (!this.isEdit)
                return;
            isEdit = false;
            this.Id = backup.Id;
        }

        public void EndEdit()
        {
            if (this.isEdit == false)
                return;
            this.isEdit = false;
            this.backup = null;
        }

        #endregion
        private int _id;
        private string _name;
        private bool isEdit;
        private Kid backup;

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

}
}

Xaml Code:

<Window x:Class="WpfApplication7.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="MainWindow" Height="350" Width="525">
<DockPanel LastChildFill="True" Margin="-1,5,1,-5">
    <Button Content="Add" Height="30" Click="Button_Click" DockPanel.Dock="Top"/>
    <DataGrid ItemsSource="{Binding Path=People}"
              AutoGenerateColumns="False"
              SelectionUnit="CellOrRowHeader"
              RowDetailsVisibilityMode="Visible">
        <DataGrid.Columns>
            <DataGridTextColumn Header="Salary" Binding="{Binding Path=Salary,StringFormat='{}{0:#,0}'}"/>
            <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
            <DataGridCheckBoxColumn Header="IsLocked" Binding="{Binding Path=IsLocked,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
        </DataGrid.Columns>

        <DataGrid.RowDetailsTemplate>
            <DataTemplate>
                <DataGrid
                    ItemsSource="{Binding Path=Kids}"
              AutoGenerateColumns="False"
                    IsReadOnly="{Binding Path=IsLocked}"
              SelectionUnit="CellOrRowHeader">
                    <DataGrid.Columns>
                        <DataGridTextColumn Header="Id" Binding="{Binding Path=Id,StringFormat='{}{0:#,0}'}"/>
                        <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
                    </DataGrid.Columns>
                </DataGrid>
            </DataTemplate>

        </DataGrid.RowDetailsTemplate>
    </DataGrid>
</DockPanel>

mchicago
  • 780
  • 9
  • 21
  • Tab out will invoke the DG's internal end-of-edit method. If you do not tab out, but click with your mouse in another row, does the same behaviour occur? – Gayot Fow Jul 29 '13 at 22:42
  • @GarryVass. You are right. The exception only occurs upon pressing tab not upon mouse usage. – mchicago Jul 30 '13 at 03:11
  • Also the internal transaction control on a DG will override your OnPropertyChanged to make it Explicit. – Gayot Fow Jul 30 '13 at 06:20
  • The remaining step is to implement the editable callbacks on your Person and Kid class so to capture the edit and avoid the null reference. If you haven't done that before, it's truly best to study that aspect of the DG until you totally understand it; otherwise the problem will persist the next time. – Gayot Fow Jul 30 '13 at 08:29
  • @GarryVass I am not sure about how implementing IEditableObject will help me out here? Because everything works fine if the parent row is not locked initially. Can you be more explicit about explanation? – mchicago Jul 30 '13 at 14:46
  • Have you tried capturing the Cancel and End callbacks? – Gayot Fow Jul 30 '13 at 14:51
  • @GarryVass I am working on a MVVM application so I wouldn't prefer capturing cancel/end callbacks in code behind. Secondly here looks like the datagrid in details view is partially initialized while changing from readonly to editable. Because for the parent rows which are not readonly there won't be any problems. This problem occurs only when the grid is changed from readonly to editable. – mchicago Jul 30 '13 at 18:56
  • Don't understand. My apps are orthodox MVVM and I use it all the time. Don't understand where you're coming from on that one... – Gayot Fow Jul 30 '13 at 19:02
  • @GarryVass instead of sending multiple messages to each other can you please explain me the logic about resolving the issue? Thanks. – mchicago Jul 31 '13 at 14:04
  • implement IEditableObject on your 'Person' and 'Kid' classes, and implement INotifyPropertyChanged on your 'Kid' class. Then shout if it doesn't work – Gayot Fow Jul 31 '13 at 15:46
  • @GarryVass I did try to what you suggested but still I got the same error. Thanks for your help but looks like DataGrid in details view is not fully initialized after changing the state of IsReadOnly. – mchicago Jul 31 '13 at 19:37

2 Answers2

0

you can change the Type of Id from int to string

if Id column is Unique you have to make it ReadOnly

Or you can Handle it By IvalueConverter

Follow this

Create a class and Implement IValueConverter

public class IntConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return value;
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value == null || value.ToString()=="")
            return 0;
        return value;
    }
}

then Add the Coverter as a resource to your Window

xmlns:local="clr-namespace:WpfApplication1"

and

<Window.Resources>
    <local:IntConverter x:Key="converter" />
</Window.Resources>

Finally Add the Converter to your Id Column

<DataGridTextColumn Header="Id" Binding="{Binding Path=Id,StringFormat='{}{0:#,0}',Converter={StaticResource converter}}" />
mohammad jannesary
  • 1,811
  • 1
  • 26
  • 27
0

Try to set the TargetNullValue on the Id column and make the Kid.Id a nullable int then you're problem should be resolved.

For information about the TargetNUllValue Property see: http://msdn.microsoft.com/en-us/library/system.windows.data.bindingbase.targetnullvalue.aspx

Code behind:

public class Kid : IEditableObject, INotifyPropertyChanged
    {
        public int? Id
        {
            get { return _id; }
            set
            {
                this._id = value;
                if (PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Id"));

            }

        }
        public string Name
        {
            get { return _name; }
            set
            {
                this._name = value;
                if (PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs("Name"));

            }
        }
        public Person Parent { get; set; }

        public Kid()
        {
            this.Id = 12345;
            this.Name = "kidname";
        }

        public Kid(int? id, string name = "kidname")
        {
            this.Id = id;
            this.Name = name;
        }

        #region IEditableObject Members

        public void BeginEdit()
        {
            if (isEdit) return;
            isEdit = true;
            this.backup = this.MemberwiseClone() as Kid;
        }

        public void CancelEdit()
        {
            if (!this.isEdit)
                return;
            isEdit = false;
            this.Id = backup.Id;
        }

        public void EndEdit()
        {
            if (this.isEdit == false)
                return;
            this.isEdit = false;
            this.backup = null;
        }

        #endregion
        private int? _id;
        private string _name;
        private bool isEdit;
        private Kid backup;

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }

WPF: (Make sure you have the system: reference added

<Window x:Class="WpfApplication3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:system="clr-namespace:System;assembly=mscorlib"
        Title="MainWindow" Height="350" Width="525">
    <DockPanel LastChildFill="True" Margin="-1,5,1,-5">
        <Button Content="Add" Height="30" Click="Button_Click" DockPanel.Dock="Top"/>
        <DataGrid ItemsSource="{Binding Path=People}"
              AutoGenerateColumns="False"
              SelectionUnit="CellOrRowHeader"
              RowDetailsVisibilityMode="Visible">
            <DataGrid.Columns>
                <DataGridTextColumn Header="Salary" Binding="{Binding Path=Salary,StringFormat='{}{0:#,0}'}"/>
                <DataGridTextColumn Header="Name" Binding="{Binding Path=Name}"/>
                <DataGridCheckBoxColumn Header="IsLocked" Binding="{Binding Path=IsLocked,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </DataGrid.Columns>

            <DataGrid.RowDetailsTemplate>
                <DataTemplate>
                    <DataGrid
                    ItemsSource="{Binding Path=Kids}"
              AutoGenerateColumns="False"
                    IsReadOnly="{Binding Path=IsLocked}"
              SelectionUnit="CellOrRowHeader">
                        <DataGrid.Columns>
                            <DataGridTextColumn Header="Id" Binding="{Binding Path=Id,StringFormat='{}{0:#,0}', TargetNullValue={x:Static system:String.Empty}}"/>
                            <DataGridTextColumn  Header="Name" Binding="{Binding Path=Name}"/>
                        </DataGrid.Columns>
                    </DataGrid>
                </DataTemplate>

            </DataGrid.RowDetailsTemplate>
        </DataGrid>
    </DockPanel>
</Window>
Martijn van Put
  • 3,293
  • 18
  • 17