Le contexte :
J'ai une classe Toto que voici :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
class Toto
{
	private NpgsqlTypes.NpgsqlInterval _interval;
 
	public NpgsqlTypes.NpgsqlInterval Interval {
		get { return _interval; }
		set { _interval = value; }
	}
}
On fait difficilement plus simple dans le cas de cette exception.

Cette classe me sert de source de donnée pour un DataGridView, via un BindingSource et une BindingList<Toto>.

J'ai à côté de ça une colonne customisée NpgsqlIntervalPickerColumn qui affiche un contrôle utilisateur NpgsqlIntervalPicker. Le NpgsqlIntervalPicker utilise la donnée de type NpgsqlInterval.
Tous ces Types n'existaient pas, j'en avais besoin, je les ais donc créés. (Je peux fournir le code de ces types si nécessaire pour la résolution de ce problème).

Le problème :
Lors de l'édition de la dernière ligne contenant des données du contrôle DataGridView (c'est à dire pas la NewRow, mais celle juste avant), j'ai une exception qui survient plus ou moins aléatoirement.
Si je n'ai qu'une seule ligne dans le DataGridView (donc 3 lignes en comptant la header et la newrow), l'exception surviendra toujours du premier coup.
Si j'ai davantage de lignes, l'exception semble survenir aléatoirement avant 10 éditions successives (c'est à dire parfois après la 1ère édition, d'autres fois après la 5e, 6e, etc...)

Le détail de l'exception :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
L'exception System.InvalidOperationException n'a pas été gérée
  Message="L'opération n'est pas valide en raison de l'état actuel de l'objet."
  Source="System.Windows.Forms"
  StackTrace:
       à System.Windows.Forms.DataGridView.DataGridViewDataConnection.ProcessListChanged(ListChangedEventArgs e)
       à System.Windows.Forms.DataGridView.DataGridViewDataConnection.currencyManager_ListChanged(Object sender, ListChangedEventArgs e)
       à System.Windows.Forms.CurrencyManager.OnListChanged(ListChangedEventArgs e)
       à System.Windows.Forms.CurrencyManager.List_ListChanged(Object sender, ListChangedEventArgs e)
       à System.Windows.Forms.BindingSource.OnListChanged(ListChangedEventArgs e)
       à System.Windows.Forms.BindingSource.InnerList_ListChanged(Object sender, ListChangedEventArgs e)
       à System.ComponentModel.BindingList`1.OnListChanged(ListChangedEventArgs e)
       à System.ComponentModel.BindingList`1.InsertItem(Int32 index, T item)
       à System.Collections.ObjectModel.Collection`1.Add(T item)
       à System.ComponentModel.BindingList`1.AddNewCore()
       à System.ComponentModel.BindingList`1.System.ComponentModel.IBindingList.AddNew()
       à System.Windows.Forms.BindingSource.AddNew()
       à System.Windows.Forms.CurrencyManager.AddNew()
       à System.Windows.Forms.DataGridView.DataGridViewDataConnection.AddNew()
       à System.Windows.Forms.DataGridView.DataGridViewDataConnection.OnNewRowNeeded()
       à System.Windows.Forms.DataGridView.OnRowEnter(DataGridViewCell& dataGridViewCell, Int32 columnIndex, Int32 rowIndex, Boolean canCreateNewRow, Boolean validationFailureOccurred)
       à System.Windows.Forms.DataGridView.SetCurrentCellAddressCore(Int32 columnIndex, Int32 rowIndex, Boolean setAnchorCellAddress, Boolean validateCurrentCell, Boolean throughMouseClick)
       à System.Windows.Forms.DataGridView.ProcessDownKeyInternal(Keys keyData, Boolean& moved)
       à System.Windows.Forms.DataGridView.ProcessEnterKey(Keys keyData)
       à System.Windows.Forms.DataGridView.ProcessDialogKey(Keys keyData)
       à Helios.FixedDataGridView.ProcessDialogKey(Keys keyData) dans C:\Users\David\Projets\Helios\Helios\FixedDataGridView.cs:ligne 12
       à System.Windows.Forms.Control.ProcessDialogKey(Keys keyData)
       à System.Windows.Forms.ContainerControl.ProcessDialogKey(Keys keyData)
       à System.Windows.Forms.Control.ProcessDialogKey(Keys keyData)
       à System.Windows.Forms.ContainerControl.ProcessDialogKey(Keys keyData)
       à System.Windows.Forms.TextBoxBase.ProcessDialogKey(Keys keyData)
       à System.Windows.Forms.Control.PreProcessMessage(Message& msg)
       à System.Windows.Forms.Control.PreProcessControlMessageInternal(Control target, Message& msg)
       à System.Windows.Forms.Application.ThreadContext.PreTranslateMessage(MSG& msg)
       à System.Windows.Forms.Application.ThreadContext.System.Windows.Forms.UnsafeNativeMethods.IMsoComponent.FPreTranslateMessage(MSG& msg)
       à System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
       à System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       à System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       à System.Windows.Forms.Application.Run(Form mainForm)
       à test.Program.Main() dans C:\Users\David\Projets\Helios\test\Program.cs:ligne 16
       à System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       à System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       à Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       à System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       à System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       à System.Threading.ThreadHelper.ThreadStart()
J'ai cherché sur internet si d'autres avaient eu un souci similaire, mais il semble que ma situation soit suffisemment spécifique pour que cela ne soit pas le cas.
Tout ce que j'ai trouvé d'approchant, c'est un "bug" du DataGridView avec la touche Echap (Keys.Escape).

Alors, j'ai été farfouiller le code source du framework .NET avec reflector, et voici ce sur quoi je suis tombé :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
[Designer("System.Windows.Forms.Design.DataGridViewDesigner, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"), ClassInterface(ClassInterfaceType.AutoDispatch), DefaultEvent("CellContentClick"), ComplexBindingProperties("DataSource", "DataMember"), Docking(DockingBehavior.Ask), SRDescription("DescriptionDataGridView"), Editor("System.Windows.Forms.Design.DataGridViewComponentEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(ComponentEditor)), ComVisible(true)]
public partial class DataGridView : Control, ISupportInitialize
{
	internal partial class DataGridViewDataConnection
	{
		private void ProcessListChanged(ListChangedEventArgs e) {
			if (((e.ListChangedType == ListChangedType.PropertyDescriptorAdded) || (e.ListChangedType == ListChangedType.PropertyDescriptorDeleted)) || (e.ListChangedType == ListChangedType.PropertyDescriptorChanged)) {
				this.dataConnectionState [2] = true;
				try {
					this.DataSourceMetaDataChanged();
				} finally {
					this.dataConnectionState [2] = false;
				}
			} else if (this.dataConnectionState [0x10000] != this.owner.AllowUserToAddRowsInternal) {
				this.dataConnectionState [0x400] = true;
				try {
					this.owner.RefreshRows(!this.owner.InSortOperation);
					this.owner.PushAllowUserToAddRows();
				} finally {
					this.ResetDataConnectionState();
				}
			} else if (this.dataConnectionState [4] || (this.owner.newRowIndex != e.NewIndex)) {
				if ((e.ListChangedType == ListChangedType.ItemAdded) && (this.currencyManager.List.Count == (this.owner.AllowUserToAddRowsInternal ? (this.owner.Rows.Count - 1) : this.owner.Rows.Count))) {
					if (this.dataConnectionState [0x1000] && this.dataConnectionState [0x2000]) {
						this.dataConnectionState [0x4000] = true;
					}
				} else {
					if (e.ListChangedType == ListChangedType.ItemDeleted) {
						if ((this.dataConnectionState [0x1000] && this.dataConnectionState [0x4000]) && this.dataConnectionState [0x2000]) {
							this.dataConnectionState [0x4000] = false;
						} else {
							if (!this.dataConnectionState [4] && this.dataConnectionState [0x8000]) {
								this.dataConnectionState [0x400] = true;
								try {
									this.owner.RefreshRows(!this.owner.InSortOperation);
									this.owner.PushAllowUserToAddRows();
								} finally {
									this.dataConnectionState [0x400] = false;
								}
								return;
							}
							if (this.currencyManager.List.Count == this.DataBoundRowsCount()) {
								return;
							}
						}
					}
					this.dataConnectionState [0x10] = true;
					try {
						switch (e.ListChangedType) {
							case ListChangedType.Reset: {
									this.dataConnectionState [0x400] = true;
									bool visible = this.owner.Visible;
									if (visible) {
										this.owner.BeginUpdateInternal();
									}
									try {
										this.owner.RefreshRows(!this.owner.InSortOperation);
										this.owner.PushAllowUserToAddRows();
										this.ApplySortingInformationFromBackEnd();
										goto Label_05A2;
									} finally {
										this.ResetDataConnectionState();
										if (visible) {
											this.owner.EndUpdateInternal(false);
											this.owner.Invalidate(true);
										}
									}
									break;
								}
							case ListChangedType.ItemAdded:
								break;

							case ListChangedType.ItemDeleted:
								this.owner.Rows.RemoveAtInternal(e.NewIndex, true);
								this.dataConnectionState [0x2000] = false;
								goto Label_05A2;

							case ListChangedType.ItemMoved: {
									int lo = Math.Min(e.OldIndex, e.NewIndex);
									int hi = Math.Max(e.OldIndex, e.NewIndex);
									this.owner.InvalidateRows(lo, hi);
									goto Label_05A2;
								}
							case ListChangedType.ItemChanged: {
									string name = null;
									if (e.PropertyDescriptor != null) {
										name = e.PropertyDescriptor.Name;
									}
									for (int i = 0 ; i < this.owner.Columns.Count ; i++) {
										DataGridViewColumn column = this.owner.Columns [i];
										if (column.Visible && column.IsDataBound) {
											if (!string.IsNullOrEmpty(name)) {
												if (string.Compare(column.DataPropertyName, name, true, CultureInfo.InvariantCulture) == 0) {
													this.owner.OnCellCommonChange(i, e.NewIndex);
												}
											} else {
												this.owner.OnCellCommonChange(i, e.NewIndex);
											}
										}
									}
									if ((this.owner.CurrentCellAddress.Y == e.NewIndex) && this.owner.IsCurrentCellInEditMode) {
										this.owner.RefreshEdit();
									}
									goto Label_05A2;
								}
							default:
								goto Label_05A2;
						}
						if ((this.owner.NewRowIndex != -1) && (e.NewIndex == this.owner.Rows.Count)) {
							throw new InvalidOperationException();
						}
						this.owner.Rows.InsertInternal(e.NewIndex, this.owner.RowTemplateClone, true);
					Label_05A2:
						if (((this.owner.Rows.Count > 0) && !this.dataConnectionState [8]) && !this.owner.InSortOperation) {
							this.MatchCurrencyManagerPosition(false, e.ListChangedType == ListChangedType.Reset);
						}
					} finally {
						this.dataConnectionState [0x10] = false;
					}
				}
			} else if (e.ListChangedType == ListChangedType.ItemAdded) {
				if (!this.dataConnectionState [0x200] && !this.dataConnectionState [0x100]) {
					if (this.owner.Columns.Count > 0) {
						do {
							this.owner.newRowIndex = -1;
							this.owner.AddNewRow(false);
						}
						while (this.DataBoundRowsCount() < this.currencyManager.Count);
					}
					this.dataConnectionState [4] = true;
					this.MatchCurrencyManagerPosition(true, true);
				}
			} else if (e.ListChangedType == ListChangedType.ItemDeleted) {
				if (this.dataConnectionState [0x40]) {
					this.owner.PopulateNewRowWithDefaultValues();
				} else {
					if (this.dataConnectionState [0x8000] || this.dataConnectionState [0x200]) {
						this.dataConnectionState [0x400] = true;
						try {
							this.owner.RefreshRows(!this.owner.InSortOperation);
							this.owner.PushAllowUserToAddRows();
							return;
						} finally {
							this.dataConnectionState [0x400] = false;
						}
					}
					if (this.dataConnectionState [0x1000] && (this.currencyManager.List.Count == 0)) {
						this.AddNew();
					}
				}
			}
		}
	}
}
J'attire votre attention sur le code en gras, c'est de là que provient l'exception.

On a donc :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
if((this.owner.NewRowIndex != -1) && (e.NewIndex == this.owner.Rows.Count))throw new InvalidOperationException();
Sachant que :
  • DataGridView.NewRowIndex est toujours différent de -1, excepté dans le cas où l'ajout de lignes a été désactivé dans le DataGridView (ce qui n'est pas le cas ici)
  • e.NewIndex ne devrait jamais être égal à this.owner.Rows.Count dans le cas de l'édition d'une ligne (ce qui est le cas ici) puisqu'on ne peut pas éditer une ligne qui n'existe pas et que les index des lignes existantes vont de 0 à this.owner.Rows.Count - 1 (et même - 2, si on enlève NewRow)

Comment cette exception peut-elle se produire ??!