C# Wpf datagrid在运行时将状态从只读更改为可编辑

C# Wpf datagrid在运行时将状态从只读更改为可编辑,c#,wpf,datagrid,rowdetails,C#,Wpf,Datagrid,Rowdetails,我还不熟悉WPF开发。我只是快速创建了一个示例来重新创建场景。我正在根据父行中的复选框切换rowdetailsview中datagrid的IsReadonly属性 除了一个特定的场景外,一切都正常 如何重现问题 保持初始创建父行处于选中状态。 取消选中父行。 转到子行id属性。 清除该单元格中id字段和选项卡中的所有内容,您将看到空引用异常 我不知道如何解决这个问题。任何见解都会非常有用 代码隐藏代码: namespace WpfApplication7 {/// <summary>

我还不熟悉WPF开发。我只是快速创建了一个示例来重新创建场景。我正在根据父行中的复选框切换rowdetailsview中datagrid的IsReadonly属性

除了一个特定的场景外,一切都正常

如何重现问题

保持初始创建父行处于选中状态。 取消选中父行。 转到子行id属性。 清除该单元格中id字段和选项卡中的所有内容,您将看到空引用异常

我不知道如何解决这个问题。任何见解都会非常有用

代码隐藏代码:

 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代码:

<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>

您可以将Id的类型从int更改为string

如果Id列是唯一的,则必须将其设置为只读

或者你可以用IvalueConverter来处理

照此

创建一个类并实现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;
    }
}
然后将转换器作为资源添加到窗口中

xmlns:local="clr-namespace:WpfApplication1"

最后,将转换器添加到Id列中


尝试在Id列上设置TargetNullValue,并将Kid.Id设置为可为null的int,然后您的问题应该得到解决

有关TargetNUllValue属性的信息,请参阅:

代码隐藏:

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:确保添加了system:reference

<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>

Tab out将调用DG的内部end of edit方法。如果您不进行制表,而是在另一行单击鼠标,是否会出现相同的行为?@GarryVass。你是对的。此异常仅在按下tab键时发生,而不是在使用鼠标时发生。此外,DG上的内部事务控件将覆盖OnPropertyChanged以使其显式化。剩下的步骤是在Person和Kid类上实现可编辑回调,以便捕获编辑并避免空引用。若你们以前并没有这样做过,那个么最好研究DG的那个方面,直到你们完全理解它;否则问题将在下一次继续存在。@GarryPass我不确定实现IEditableObject将如何帮助我解决这个问题?因为如果父行最初未锁定,则一切正常。你能更明确地解释一下吗?
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
    }
<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>