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.
Related
I am having a form which is used to either create/ edit new Departments. I have a functional requirement to unhide few fields after few fields are filled with data.
When I open two Departments to edit, one department has the first set of fields unfilled and hence it hides the second set of fields.
In the second Department which has first set of fields filled, I can see (visible) the second set of fields (which is the way it is coded). But now if I go back to the first Department(already opened in another tab)(open the tab, we coded to refresh the tab on focus), which initially was not filled with the required details, I see that the second set of fields are enabled (of course the filed are yet unfilled).
I am sure that they have different transactions and also different instances but unable to understand this behavior, any idea on how to debug this?
If you are using the same VO for the same AM/DC then yes, they are sharing the same data (VO Rowset and Binding integrator) so changes to one will be reflected dint he other.
If I understand your problem correctly, one solution would be to create separate Task Flows, one for each Page and then convert the Bounded taksflows with page fragments. and here. Then drop these task flows (as Regions) onto the respective tables. Make sure to mark the task flow as Always Use New Transaction and here and Data Control Scope/Frame as Isolated and here. If the two task flows need to communicate with each other use a contextual event here and here.
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!
This is more of a conceptual question. If it helps, lets say it falls into the MVVM pattern. I've never seen this addressed in any of my research into WPF Binding.
If you have a window where you have elements connected to view-model through binding and the user updates the record, but before saving decides to cancel the update, what would you consider the best method of reversing the changes would be?
I can think of a couple of possibilities:
Instead of allowing the user to update the main display, have a second window pop up for editing that is bound to a copy of the VM and upon "Save", writes that data into the original VM and then updates back to the model. Increases memory usage and complexity of the Save function.
Make a "pristine" copy of the original VM object and, upon "Cancel", write that VM back over the original (at which time the copy can be deleted). Increases memory usage and complexity of the Cancel function.
Create a copy of each of the elements within the VM that might change upon creation and upon "Cancel", write those values back over the changed ones. Increases the size and complexity of the VM.
Upon executing "Cancel", the VM hits the data source and reloads the record. Causes network traffic and DB access traffic and possibly lag time for display updating back to original state. Also might cause problems if the VM was updated from another process and these "external" updates should not be deleted.
Can anyone think of a better solution to this problem, or know of some obscure functionality within WPF that handles this "automatically"?
I think the easiest way to do this is to use a BindingGroup, which will delay updates to the VM until the user explicitly commits the changes. This way, the controls will cache the changed values, and the BindingGroup will be able to restore the original values from the (still unchanged) VM.
I have implemented the IEditableObject on my ViewModels and I make a copy of the fields when entering Edit mode by invoking an EditCommand.
I also have SaveCommands that can be called in when confirming the edit. Or use a CancelEdit command to revert to the copied values.
The key thing is the model should be able to rollback the user changes. How do you do it is really a matter of taste. You can go back to its unchanged state by uisng a 'pristine' copy (not all objects are easily clonable) you can re-query the database, in which can you've a chance to end up with something different to what you initially attempted to edit. There's no universal solution for the problem.
I have a view that uses a usercontrol that contains a datagrid. The requirements for the view state that if a user deletes the value out of the "Customer Name" column then leaves the cell, don't commit the edit and change the value back to its original value - i.e. don't allow blank customer names. The usercontrol is shared code and is used between multiple applications. The edit cancellation requirement is specifically for the view mentioned above (not all applications that use the usercontrol). How do i detect the value for the Customer Name cell has been deleted and cancel the edit if the value is empty?
You can handle CellEditEnding - its Occurs before a cell edit is committed or canceled. Here you can validate current value of the cell, and if it satisfy your condition[s] you can do what ever you want. MSDN
Edit:
Its my suggestions, but I can't approve it.
You can get your new value via e.Row.Item, and cast it to object you put on DataGrid.
Another way is cast sender object right way to get access for new cell value.
+1 is to DataGridCellEditEndingEventArgs.EditingElement. Its FrameworkElement, and I think you know what it is (probably TextBox, or something else).
also I think you know what you should to do!
I have a Microsoft Access form that is bound to a linked SQL Server table that has a computed column. I have a control bound to the computed column.
I already realize that the computed field cannot be refreshed until AFTER a record is saved. Beyond that, what is the best way to refresh that textbox that is bound to the computed column after save.
I would prefer not to do a me.requery (a requery of the whole recordset).
Is there a way to JUST refresh that one field?
EDITED FOR CLARITY: There are actually a few strategies to consider.
Form.Refresh() will refresh your Form's recordsource capturing modifications and deletions to existing records and will stay positioned on the current record. However, you would not see any NEW records that were added since you opened your form.
Form.Requery() will re-run the Form's recordsource query. You will see all the Form.Refresh() changes AND it will show you any new records. On the UI, Form.Requery() repositions to the first record.
Form.Control.Requery() is similar to Form.Refresh() in that you will not change record position or see NEW records. It will update your control, assuming the control is based on a query/table.
You'll need to be sure that the triggering event involves a database update. There can be cases where the control's AfterUpdate() precedes database I/O, which wouldn't help you.
Thanks guys. Here it what worked.
In the Form AfterUpdate event I did a Me.ControlName.Requery. This was perfect as it did not do a complete form refresh or requery.
I just experimented with different event/method combinations until I got the best result.
Thanks for the input.
Seth
2016 (related) Answer: In Access 2016, I have a control that is a calculated field based on values in 4 other bound textboxes. I set the Control Source for the calculated field to be =gsngCalculatePaymentAmount([txtInterestRate],[txtLengthOfLoanInYears],[txtSalePrice],[txtMoneyDown]).
But I needed the value to be refreshed whenever ANY of the values for any of those 4 textboxes changed. Had difficulty in making the calculated value refresh. SOLUTION: In the AfterUpdate event in EACH of the 4 textboxes whose value was being used for the calculated field, I simply did the following:
Dim v1 As Variant
v1 = Me.txtMyCalculatedTextbox
Simply referencing the value of the calculated field resolved it!
Rex
Have you tried .Refresh for either the underlying recordset, or for the bound control? I would expect it to work for a Jet/ACE back end, but the interaction with different database server back ends is going to be non-predictable.
There might also be an interaction with you ODBC refresh interval, but I would expect a manual refresh in code to take care of that.
Where to call it is another issue -- I would assume the appropriate place would be in the AfterUpdate events of the controls bound to the fields the calculation is based on. But you might have to save the record for the server-side calculation to happen -- Refresh alone might or might not do the trick. I know with Jet/ACE data sources a Refresh saves the record, but I don't know for certain if it behaves the same way with ODBC data sources.
I did it this way (the control source of txtTextBox is an SQL Server field whose value is computed):
On Error Goto ErrorHandler
'After saving data when I wanted to see the updated value of computed field
txtTextBox = txtTextBox & ""
ErrorHandler: If err.Number = -2147352567 Then 'The data has been changed.
Resume Next
else
msgbox err.description
end if