An item with the same key has already been added error - wpf

I know the error is a common one but i'm not sure how to solve this.
my scenario is this:
click button on window 1, window 2 will show (populates text boxes).
finish transaction on window 2 (save data).
click BACK button on window 2 (closes window 2, opens window 1).
again, click button on window 1 to open and populate data on window 2.
error fires. An item with the same key has been added.

It sounds like you are adding data to something in Window 2 that is shared across the entire application, or all instance of Window 2. When you enter Window 2 for a second time and populate your data, you are likely adding the data into a Dictionary that has already been added. Hence the message "An item with the same key has already been added".
My advice: put a break point in the code where you populate the data, and check the values contained within the Dictionary (if you have used one and have access to it). Then check the data you are adding and you should find the replication.
Alternately, there are various ways to prevent duplicate entries being added.
Clean the Dictionary when you have finished with it the first time - this way you only add the data in once (unless you have duplicate entries in your source data).
See if the key exists within the Dictionary before adding the entry. You can do this by using if (mSomeDictionary.ContainsKey(someEntryKey))/
If you simply want to use the latest values, you can just override the data keyed with a certain object. You can do this by using something like: mSomeDictionary[someEntryKey] = someValue;. If the entry key doesn't already exist, a new entry will automatically be added.
If none of the above helps you at all, post the code where the error occurs and include the Stack Trace from within the thrown exception and we can look into it further.

Related

VB.Net ArrayList

I'm trying to wrap my head around array lists in vb.net. I am self teaching via the internet but can't seem to figure it all out. Some points i'm having trouble connecting the dots to:
How do i make the array list universal so it's not stuck in a subroutine and I can allow any sub to access the list.
Allowing the list to be added to or removed from from another control on the form.
Saving this array list so the program will populate the list box with it on startup.
Here is an image of the basic concept for the visual:
https://imgur.com/lBbopD8
Up to question 3, using a List(Of T) was the way to go. It may still be but certainly not completely and maybe not at all. Before the advent of the List(Of T), Microsoft recognised that storing Strings in a collection was the most common requirement so, to provide type-safety in that case, they provided the StringCollection class. You say that you want to persist your list of values between sessions so that probably means using My.Settings and it is actually possible to create a setting of type StringCollection.
I would suggest that you open the Settings page of the project properties and add a setting of type StringCollection. Once added, that list will be automatically loaded at startup and saved at shutdown, with no code required from you. You can access it anywhere in the app via My.Settings and you can call Add and Remove or index it or loop over it in exactly the same way as you would an ArrayList or List(Of String).
There is one small gotcha with a StringCollection in settings though. It will actually be Nothing by default. The trick to avoiding that is to edit its Value on the Settings page to add an item, commit that, then edit it again to remove the item. You'll see that, instead of the Value field being empty, it will then contain a snippet of XML. It's that that creates the StringCollection object in the settings file.
As I said, if you want to persist this list between sessions then I strongly recommend using settings this way. Just note that, in order to edit settings, they have to be User-scoped rather than Application-scoped. What that means is that each separate Windows user will have their own copy of the setting and thus their own value. If you only log into Windows with one account then it's of no consequence. If multiple Windows users use the app then it may be considered beneficial in most cases but may be a problem if you want universal settings that can be edited. If it's a problem, you will need to handle persistence yourself but be aware that a standard Windows user (as opposed to an admin) won't have access to write data everywhere, which is exactly why User-scoped settings work the way they do.
Also, while you must use a StringCollection for persistence in settings, you may or may not want to use the same collection in the rest of your code. You might access the collection directly all the time or you may choose to copy the collection to a List(Of String) at startup and then copy the data back at shutdown. Unless you want to avoid committing items until shutdown, I wouldn't bother with the extra collection.
So an important thing to know is that you can directly populate and edit the listbox without having an additional ArrayList. You would use the example code below as follows:
'addTb is the text box you had in the image; this will run on button press event
ListBox1.Items.Add(addTB.Text)
If you are looking to dump ArrayList data into the list box use something like this:
'creates new arraylist and adds items to it
Dim listStuff As ArrayList = New ArrayList
listStuff.Add("Hi")
listStuff.Add(2)
'makes listStuff the datasource for your list box
ListBox1.DataSource = listStuff
Finally, if you are wanting to loop through ArrayList items use something like this:
'remember to do count - 1 or you will receive error since index will be out of range
For i = 0 To listStuff.Count - 1
If listStuff.Item(i) = "" Then
'do stuff here
End If
Next
Hopefully that helps. Let me know if I need to be more clear since this is my first stack overflow answer :)
You are using a ListBox control to visualize a collection of presumably String values from a TextBox control. The ListBox exposes the visualized collection via the Items property.
How do i make the array list universal so it's not stuck in a subroutine and I can allow any sub to access the list.
Because the ListBox control resides on the Form, you can access the Items property through any access level in your Form's code.
Allowing the list to be added to or removed from from another control on the form.
From the Items property, you can use the Add method to add a single value, the AddRange method to add multiple values via an array or another ListBox collection, the Insert method to insert a value at a given index, the Remove method a specific item, and the RemoveAt method to remove an item at a given index.
So in your case, since you're presumably adding the value from the Text property of the TextBox to the ListBox, it is as simple as:
ListBox1.Items.Add(TextBox1.Text)
Saving this array list so the program will populate the list box with
it on startup.
You have a few options, but generally the idea is to is write each value in the Items property to its respective line at a given file when the application closes and then load each value back by reading each line from the same file. Another option is to use My.Settings, though I think with your level of expertise, it would probably be better to stick with the read/write to a file option so you don't have to worry about some pitfalls associated with this option. Here would be a quick example of reading/writing the items to a file:
'Write the items to the file
Dim items(ListBox1.Items.Count - 1) As String
ListBox1.Items.CopyTo(items, 0)
IO.File.WriteAllLines("file.txt", items)
'Read the items to the file
ListBox1.Items.AddRange(IO.File.ReadAllLines("file.txt"))

How to quickly populate a combobox with database data (VCL)

I have read all sorts of documents across the web about what should be a farly common and painless implementation. Since I have found no consistent and slick reply (even the Embarcadero website describes some properties wrong!) I am going to post a "short" howto.
Typical, frequent use case: the developer just wants to painlessy show a couple of database-extracted information in a combo box (i.e. a language selection), get the user's choice out of it and that's it.
Requirements:
Delphi, more or less any version. VCL is covered.
A database table. Let's assume a simple table with an id and value fields.
A DataSet (including queries and ClientDataSets).
A DataSource linked to the DataSet.
A TDBLookupComboBox linked to the DataSource that shall show a list of values and "return" the currently selected id.
Firstly, we decide whether the sort order is the one we want or not and if all the items in that table must be shown. Most often, a simple ORDER BY clause or a DataSet index will be enough. In case it's not, we may add a sort_order field and fill it with integers representing our custom sort order. In case we want to show just some items, we add a visible or enabled field and use it in our SQL. Example:
SELECT id, value
FROM my_database_table
WHERE visible = 1
ORDER BY sort_order
I have defined visible as INTEGER and checking it against 1 and not TRUE because many databases (including the most used SQLite) have little support for booleans.
Then an optional but surprisingly often nice idea: temporarily add a TDBGrid on the form, link it to the same TDataSource of the TLookupComboBox and check that you actually see the wanted data populate it. In fact it's easy to typo something in a query (assuming you are using a SQL DataSet) and get no data and then you are left wondering why the TDBLookupComboBox won't fill in.
Once seen the data correctly show up, remove the grid.
Another sensible idea is to use ClientDataSets for these kinds of implementations: due to how they work, they will "cache" the few rows contained in your look ups at program startup and then no further database access (and slowdown and traffic) will be required.
Now open the TDBLookupComboBox properties and fill in only the following ones:
ListSource (and not DataSource): set it to the TDataSource connected to the DataSet you want to show values of.
ListField: set it to the field name that you want the user to see. In our demo's case it'd be the value field.
KeyField: set it to the field name whose value you want the program to return you. In our demo it'd be the id field.
Don't forget to check the TabOrder property, there are still people who love navigating through the controls by pressing the TAB key and nothing is more annoying than seeing the selection hopping around randomly as your ComboBox was placed last on the form despite graphically showing second!
If all you need is to show a form and read the TDBLookupComboBox selected value when the user presses a button, you are pretty much sorted.
All you'll have to do in the button's OnClick event handler will be to read the combo box value with this code:
SelectedId := MyCombo.KeyValue;
Where SelectedId is any variable where to store the returned value and MyCombo of course is the name of your TDBLookupComboBox. Notice how KeyValue will not contain the text the user sees onscreen but the value of the id field we specified in KeyField. So, if our user selected database row was:
id= 5
value= 'MyText'
MyCombo.KeyValue shall contain 5.
But what if you need to dynamically update stuff on the form, depending un the combo box user selection? There's no OnChange event available for our TDBLookupComboBox! Therefore, if you need to dynamically update stuff around basing on the combo box choices, you apparently cannot. You may try the various "OnExit" etc. events but all of them have serious drawbacks or side effects.
One possible solution is to create a new component inheriting from TDBLookupComboBox whose only task is to make public the hidden OnChange event. But that's overkill, isn't it?
There's another way: go to the DataSet your TDBLookupComboBox is tied to (through a DataSource). Open its events and double click on its OnAfterScroll event.
In there you may simulate an OnChange event pretty well.
For the sake of demonstration, add one integer variable and a TEdit box to the form and call them: SelectedId and EditValue.
procedure TMyForm.MyDataSetAfterScroll(DataSet: TDataSet);
var
SelectedId : integer;
begin
SelectedId := MyDataSet.FieldByName('id').AsInteger;
EditValue.Text := MyDataSet.FieldByName('value').AsString;
end;
That's it: you may replace those two demo lines with your own procedure calls and whatever else you might need to dynamically update your form basing on the user's choices in your combo box.
A little warning: using the DataSet OnAfterScroll has one drawback as well: the event is called more often than you'd think. In example, it may be called when the dataset is opened but also be called more than once during records navigation. Therefore your code must deal with being called more frequently than needed.
At this point you might rub your hands and think your job is done!
Not at all! Just create a short demo application implementing all the above and you'll notice it lacks of an important finishing touch: when it starts, the combo box has an annoying "blank" default choice. That is, even if your database holds say 4 choices, the form with show an empty combo box selected value at first.
How to make one of your choices automatically appear "pre-selected" in the combo box as you and your users expect to?
Simple!
Just assign a value to the KeyValue property we already described above.
That is, on the OnFormCreate or other suitable event, just hard-code a choice like in example:
MyCombo.KeyValue := DefaultId;
For example, by using the sample database row posted above, you'd write:
MyCombo.KeyValue := 5;
and the combo box will display: "MyText" as pre-selected option when the user opens its form.
Of course you may prefer more elegant and involved ways to set a default key than hard-coding its default value. In example you might want to show the first alphabetically ordered textual description contained in your database table or whatever other criterium. But the basic mechanic is the one shown above: obtain the key / id value in any manner you want and then assign it to the KeyValue property.
Thank your for reading this HowTo till the end!

DataGrid selected item changed more quickly than data loads from DB

We have the following WPF : a datagrid with row detail template. Selected line on this grid (customer selection) is handled to trigger two queries to retrieve address and contacts data, then row detail template show these data in two tabs, each with a datagrid. All the magic is done with binding, subgrids bind to properties of main Customer object, which we have as an IObservableCollection.
The bug happens when using “move down” arrow on keyboard, selected index changes fast, so fast than queries result “come back late” to interface, thus data is incoherent to what is shown and data update cannot perform. We have an exception. So my question is : how to prevent this in a proper manner ?
Should we have a try-catch the right kind of obscure exception then do nothing (loose data that cannot be applied to interface) ?
We don’t want to wait for data to come back, if user scrolls very quickly, customer selected line should go down and no matter details aren’t shown.
Maybe we should have a delay before selected item details are retrieved ? Thus no query if selected item changes before end of delay ?
Thank you for your ideas.
Gists for code:
Xaml : https://gist.github.com/Xarkam/3b89eb614124bb2f2307
Selected index changed handling : https://gist.github.com/Xarkam/cf28844ce05fd4984807
Edit 10th of July :
I have modified the main datagrid items definition as in following gist : (add https:// prefix, I don't have enough reputation to add more links, sorry) gist.github.com/postb99/d3be79f0ef2544d685f9 (inspired from stackoverflow.com/questions/13374957/datagrid-throws-invalidoperationexception-by-scrolling and proposed answer) but problem still persists...
We've solved it, if I remember well we didn't have observable collections for every collection of objects to display.

Binding to Dictionary/Array of objects

I have large collection of statuses(bool) that are reached by key (address)
the visual should display each status as different control (for example checkboxes, buttons, radios , etc) - each control is provided with the address of the status it will display
for example
button1 <- status[55]
checkbox1 <- status[81]
..
etc
my question is if i put INotifyPropertyChanged on whole indexer(if i do it with indexer) - if one value changes does it update all the controls or only the changed one..
I want only one status change to update only one control - not all of them. Is there a way doing this?
It will update all, in Silverlight you could construct a notification that only updates one index. I cannot think of any solution which would let you keep that structure, if you map everything to objects with key and value you could internally notify of value changes...

Delete Records from Access database, error while deleting

I have the following situation: I built an Access form with a subform (which records are linked to the records of main form via certain key). When I try to delete any record in the subform, I get the following message: “Access has suspended the action because you and another user tried to change the data” (approximate translation from German). Does anyone know how to delete those records from the subform (and, respectively, from the table behind the form).
If you are currently 'editing' the current form then it will not allow the action. Editing a record can sometimes be triggered by simply clicking inside a field, or other simple actions you wouldn't normally consider 'editing'.
This is usually avoided in Access by using the RunCommand method to undo any edits before deleting the record:
DoCmd.RunCommand acCmdUndo
samjudson suggested:
DoCmd.RunCommand acCmdUndo
You can also use Me.Undo, to undo the last edit to the form in which the code runs.
Or, Me!MySubForm.Form.Undo to undo the last unsaved edit in the subform whose subform control is named "MySubForm".
You can also use Me!MyControl.Undo to cancel the last edit to a particular control.
"DoCmd.RunCommand acCmdUndo" will apply the Undo operation to the currently selected object, but you won't know for sure whether it will apply at the control or form level. Using the commands I suggested completely disambiguates what gets undone.
Keep in mind, though, that Undo will not undo edits to a control after the control's AfterUpdate event has fired, or to a form after its AfterUpdate event has fired (i.e., the data has been saved to the underlying data table).
Also check the "row locking mechanism" that you have. I haven't used Access in a while but I remember that you could use set that in the table properties. You can access those properties clicking in the famous "dot" in the upper left corner of the table to bring up its properties. Well if you're using Access, you know what I'm talking about.

Resources