Le contexte :
J'ai une classe Toto que voici :
On fait difficilement plus simple dans le cas de cette exception.Code:
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; } } }
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 :
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.Code:
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()
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é :
J'attire votre attention sur le code en gras, c'est de là que provient l'exception.Code:
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(); } } } } } }
On a donc :
Sachant que :Code:if((this.owner.NewRowIndex != -1) && (e.NewIndex == this.owner.Rows.Count))throw new InvalidOperationException();
- 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 ??!