Organization buttons for subform - sql

I am trying to design a multi-use organization button.
Below the button is a subform (named ProjectQSubF), with data. Though lets only focus on 1 set of data and let's say this data is named ProjectComplete and is a boolean Y/N (checkbox) field.
I want to design a button that when you click it, it sorts all those fields by if the check is checked (yes). If you click the button again, it sorts it by the opposite state which is unchecked (no). If you keep clicking it just goes back and forth between those two states.
How would I do this?
Also, how would I do this with dates; based on closer to now and furthest away as the two states?

create a line (or checkbox or whatever). Set it visible = false;
Use the tag value (/checked state) to hold a constant say True or False;
Name the object isSortAscending or similar;
In the click event for your button:
isSortAscending.Tag = (not cbool(isSortAscending.Tag)) 'switch state to the opposite
dim ss as string: ss = ProjectQSubF.Form.RecordSource 'get the existing data query
ss = left(ss, instr(ss, "ORDER BY") - 1) 'chop off the existing sort logic
if isSortAscending.Tag = True then
ProjectQSubF.Form.RecordSource = ss & "ORDER BY yourColumnName"
else
ProjectQSubF.Form.RecordSource = ss & "ORDER BY yourColumnName DESC"
end if
Likewise with dates, DESC means newest first, ASC (or simply the absence of "DESC" - as in my code above) means oldest first.
Irrespective of whether you're using a date or true/false column, you probably wont want to limit sorting to just this column as multiple rows may well exist for any one value of the column you're using, and rows within this column will then probably appear to be unsorted, so I'd suggest appending ", nameofMainTextColumn" after the order by statements above.
Hope this helps

Related

Is it possible to reference a listbox in a textbox on the same form?

I have an ID field which can not be autonumbered, and serves as the primary key for a table. When we put a new record in we need the next record to be numbered in one of two sequences that are pre-existing and can not be changed.
I was trying to do this:
=IIf([lst_DeviceType]="Cell Phone",DMax("DeviceNum","tbl_Cell_Tab","DeviceNum < 70000")+1,DMax("DeviceNum","tbl_Cell_Tab")+1)
Inside the field that will be used to create a device number.
What we have are cell phones and tablets, for cellphones the devices start at 10000, and for tablets they start at 70000. What I need to have happen is for a selection from a listbox, "Cell Phone" or "Tablet" to cause the next proper sequence number to be generated. ie. 10125 for "Cell Phone" but 70725 for "Tablet"
The above code returned a #Error
My question is thus two fold : One, is something wrong with my code? I am thinking that I am not properly referencing the listbox, but maybe that isn't it. Or two, can I not do this in the text box itself?
This is the code being used in the text box, I could try an OnUpdate on the form,
Which broke when I tried that using this code it was placed in the OnUpdate for the listbox, because that's the main selector:
If Me.lst_DeviceType = "Cell Phone" Then
Me.txt_DN.Value = DoCmd.RunSQL("SELECT MAX(DeviceNum)+1 FROM tbl_Cell_Tab
WHERE DeviceNum < 70000")
Else
Me.txt_DN.Value = DoCmd.RunSQL("SELECT MAX(DeviceNum)+1 FROM
tbl_Cell_Tab")
End If
Me.txt_DN.Locked = True
but I wonder if that is both the best and easiest way to do it?
Listboxes and comboboxes behave differently.
For a listbox, must include Column index when pulling value from selection: [lst_DeviceType].Column(0)
Then you will need code to save the calculated value. Instead of expression in textbox, bind textbox to ID field and use listbox AfterUpdate event:
If IsNull(Me!ID) Then
Me!ID = IIf(...)
End If
AFAIK, cannot use DoCmd.RunSQL to return a value to a variable. That's what domain aggregate functions are for.

How do I fix MS access option group problem?

I created many option groups in MS Access 2013 and I am trying to populate my table according to what is selected in the option group. So, if the user selects option 1, I want "the text" not its value ex: "1" stored in my table. I tried the following code in AfterUpdate() event and it works fine:
Private Sub Frame49_AfterUpdate()
Dim D As Integer
Select Case Me![Frame49]
Case 1
Me![Name] = "text"
D = 1
Case 2
Me![Name] = "text1"
D = 2
Case 3
Me![Name] = "text2"
D = 3
Case 4
Me![Name] = "text3"
D = 4
Case 5
Me![Name] = "text4"
D = 5
End Select
DoCmd.RunCommand acCmdSaveRecord
Rem D = Frame49.Value
End Sub
but when the end user answers the first question and tries to answer the next question, all options of the previous question get selected. How do I fix this?
Here is the file to see what I mean:
https://drive.google.com/open?id=1WjrAhXCnk961mxBuxS3RYqOUpPA_GsyL
Thanks in advance.
even though the option group only takes numeric values, you can achieve what you want by hard coding the values using if statements e.g
If Frame5 = 1 Then orukolook = "okay"
If Frame5 = 2 Then orukolook = "right"
If Frame5 = 3 Then orukolook = "fine"
orukolook is the textbox control that you want the texts to be inserted, so if the first option of the option group is selected,the text "okay" will be inserted into the textbox control, if second option then the text "right" will be inserted.
The values hard coded in the place holder oruko look,e.g okay,right, fine are the labels associated to each value in the option group.
OptionGroup frame and associated buttons/checkboxes must have a number value. Therefore OptionGroup frame must be bound to a number type field. If you want controls to reflect selection in a text field, then need code to set UNBOUND OptionGroup frame with corresponding number value. In other words, convert saved text back to number value. Code would most likely need to be in form Current event. Something like:
Me.Frame49 = Switch([Name]="text",1, [Name]="text1",2, [Name]="text2",3, [Name]="text3",4, [Name]="text4",5)
Alternatively, save number value to number fields. Text equivalent is provided by labels on form. Use lookup tables to provide text equivalent on reports or calculate the equivalents with expressions in query or textboxes. An expression like:
Choose([Name], "text", "text1", "text2", "text3", "text4")
BTY, Choose() expression can be used in place of Case structure in your original code.
Me![Name] = Choose(Me.Frame49, "text", "text1", "text2", "text3", "text4")
Also recommend using radio (option) buttons instead of checkboxes.
Other alternatives are comboboxes and listboxes instead of option groups.
Advise not to use reserved words as names for anything. Name is a reserved word. Also, avoid spaces and punctuation/special characters in naming convention.
Frame49 is bound to a database field.
When the user clicks a checkbox, the field's value (along with Frame49's value) is set to an integer.
You then change the database field's value to a string.
This causes Frame49's value to be set to that string.
Since that is an invalid value for an Option Group, all the related checkboxes show as a solid black square, representing an indeterminate state. That is not the same as a checkmark, so your observation "all options of the previous question get selected" is incorrect.
The simplest way to do what you want is to use a 1-column ListBox instead of an Option Group. You can size each ListBox so that it shows all the options as text strings.
When the user clicks an "option" to select it, the corresponding text string will be written to the database, with no VBA code involved.
When the user goes back to a previous record, the ListBoxes will all show the proper selections.
If you don't want to change how your form looks, then you must do as others suggested, and make Frame49 unbound, i.e. set its Control Source to blank.
Then when you set the database field's value to a text string, Frame49's value will remain as an integer.
If you want the ability to go back and edit earlier records, you can do it but it is beyond what I can answer here.

Datagridview - fill row from Combobox

I have set a combobox to be visible in column1 of my Datagridview. Now I'm trying to fill same row of Datagridview where Combobox appears, from Combobox_Key_Down event. This is my code for showing combobox:
Private Sub My_DGV_CellMouseClick(sender As Object, e As DataGridViewCellMouseEventArgs) Handles MY_DGV.CellMouseClick
If e.RowIndex >= 0 Then
With My_DGV
If .Columns(.Rows(e.RowIndex).Cells(e.ColumnIndex).ColumnIndex).Name = "Column1" Then
.CurrentCell = .Rows(.CurrentRow.Index).Cells(.CurrentCell.ColumnIndex)
Show_Combobox(.CurrentRow.Index, .CurrentCell.ColumnIndex) 'function that shows my Combobox in that cells
Combo.Visible = True
Else
Combo.Visible = False
End If
End With
End If
End Sub
I tried many things, but I don't know how to determine in which row Combobox appears and how give that Datagridview row my Combobox values. Someone please give me a clue of what should I do. Thanks in advance !
The first problem with your approach is that the DGV can have only one DataSource: it can either show the m:m association table or the related elements. If you include columns from one of the tables into the query for display, the table becomes non updatable and users can be confused why they cannot edit something they can see. It seems of little value they way you describe it, since they cannot see the detail data until after they make a selection.
Next, it requires another datatable to supply the details for CboColB. Since you want the DGV bound to a DataTable easy updates, you end up having to poke data into cells over and over.
Finally, consider what the user is confronted with. Using a Country table (200+ countries/locales with ISO code and name) and a list of flag colors, a table for CountryFlagColors will have hundreds and hundreds of rows (at just 2 colors per flag).
A better display might be to filter the m:m table (flagcolor) to a selected item so the user is only confronted with the data subset they are currently interested in:
The datatable used in the DGV is built from the m:m table:
The Country column is hidden.
When they choose from the CBO at the top, that is used as a RowFilter to limit the rows to the relevant ones.
In the RowValidating event, when the country cell is DBNull, copy the SelectedValue from the country combo to the DGV cell to fill in the blank
I would probably really make the user click a button and manually add a row so I could seed the country value then rather than depend on events.
It uses a DataAdapter and after adding X number of flag definitions, da.Update(dtFlagColors) applies/saves all the changes.
Ok, so that provides the core functionality to assign N color selections to define the flag colors for a country. The missing element is the 'details' for the Color item.
I added a meaningless int and string item to the Color table, one way to display these would be to create an alias in the SQL with the important details. Displaying them as discrete elements can either make the query non updatable or invites the user to edit things they cannot edit here. My silly SQL:
"SELECT Id, Name, Concat(Name , ' (' , intItem , ' ' , stritem,')') As Info from FColor"
Then use 'Info' as the display member on the CBO column in the dgv:
dc = DirectCast(dgvCF.Columns(0), DataGridViewComboBoxColumn)
dc.DataSource = dtFlagColors
dc.DisplayMember = "info"
dc.ValueMember = "id"
dgvCF.DataSource = dtSample
The combo column has its own datasource of course, in order to display one thing and use another for as the Value to give back to you. Result (the values are silly):
It is not exactly what you want, but comes close and is much simpler. It also requires almost no code for driving the associative entity. Another alternative would be to use a DGV as the second picker so you can show the extended data and manually add rows to a DGV:
If you set the dropdown style to Nothing, it looks like a text column.

Get value in textbox depending on combobox

I have searched high and low for an answer to this. Lots of places come close to what I need but try as I might I can't find exactly what I need. So, here goes. I have a combo box on a user form that reads values from a table. when a user makes a selection, depending on how far down the table the selection falls, I would like a textbox to display one of two strings. I'm currently using a toggle button which seems a bit 'clunky. I'm fairly new to vba so be gentle with me.
Me.textbox.Value = Me.combobox.Value
will give you the value of the bounded column of the selected row, usually the first one (e.g. if your combobox shows data like ID;LastName;PreName, it shows the ID)
If you want to show other colums than the bounded one, use
Me.textbox.Value = Me.combobox.Column(n)
(n stands for the column, beginning at 0 for the first, 1 for the second, ...)
So if you want to show the LastName of the previous example, go with
Me.textbox.Value = Me.combobox.Column(1)
If you just want to show the string when it doesn't fit in the combobox column, I'd make it like
If Len(Nz(Me.combobox.Column(1))) > n Then
Me.textbox.Value = Me.combobox.Column(1)
Else
Me.textbox.Value = Null
End If
There might be another solution, but this would be an easy one

MS Access Can't go to specified record because of another control's VB where clause

I have a lookup listbox which is programmed to allow the user to find a specific record/help topic from the list and view it. Now when the list box is used the where clause locks in the record and the first, previous, next, last buttons freeze up and you cannot use them to go to a record. Is there a way to free up the functionality of the buttons to navigate through the records along with the where clause to select.
Here is the code that operates the listbox selections:
Private Sub List35_AfterUpdate()
Dim myTopic As String
myTopic = "Select * from FormsHelpTable where ([ID] = " & Me.List35 & ")"
Me.Form.RecordSource = myTopic
Me.Comment.Requery
End Sub
I believe since the where clause locks in the selection in the box it does not allow navigation from other controls to interfere. What might be a way around this?
You get the runtime error:
You can't go to specified record.
It appears not to be reading the other record in the source table named Help once it updates using the listbox.
Instead of changing the recordset (this is the 'pool' of records which the form could display), you just need to go to the record the user selects from the listbox.
Method 1 (probably the easiest way if your listbox is in the same order as the records of your form)
Use this code:
Private Sub lstSelect_AfterUpdate()
DoCmd.GoToRecord acDataForm, "HelpForm", acGoTo, Me.lstSelect.ListIndex + 1
End Sub
You need to ensure that:
The recordset of the form is ordered the same as the listbox. So, for example, you could order both by ID, or by Title.
Note that the +1 comes from the fact that the ListIndex starts at 0, whereas the record indexes start at 1.
Method 2
Ensure each record's Title is unique (set No Duplicates on this field).
Change your listbox to return the Title of the record, rather than it's ID.
Then use this:
Private Sub lstSelect_AfterUpdate()
Me.Title.SetFocus
DoCmd.FindRecord Me.lstSelect
Me.lstSelect.SetFocus
End Sub
Things to note:
It works by searching the field with focus for the string specified. That's why we have to SetFocus on the Title textbox on our form.
We could use ID instead, (which would mean we could have duplicate titles if we wanted), but then we would have to have an ID control on the form to SetFocus to. You can't hide this control either, because it needs to have focus whilst using FindRecord.
Update: Method 1 with reverse-selection
Add an Event Procedure in the Form_Current event, with this code. Then update the code in the lstSelect_AfterUpdate procedure as shown after.
Private Sub Form_Current()
Me.lstSelect = Me.lstSelect.Column(0, Form.CurrentRecord - 1)
End Sub
Note that, depending on how your lstSelect is set up, it may be Column(1, Form.CurrentRecord - 1) instead. Read on for details!
Private Sub lstSelect_AfterUpdate()
DoCmd.GoToRecord acDataForm, "HelpForm", acGoTo, Me.lstSelect.ListIndex + 1
Me.lstSelect = Me.lstSelect.Column(0, Form.CurrentRecord - 1)
End Sub
Explanation of new lines:
The Form_Current event fires every time we go to a new record. We need to look at the index of the record (ie. the position of it in the recordset), which we get by using Form.CurrentRecord. We then want to make the listbox select that record. However, we can't use me.lstSelect.ListIndex as before, because that is a read-only property (we can access it to read it, but we can't set it).
Therefore, we use me.lstSelect.Column(colNum,rowNum) instead, where we specify a column number and a row number. If your listbox has two columns (eg. ID and Title), we want to choose the second column. The index starts at 0, so we would use a value of 1. If, like my lstSelect, you only have one column (Title) then we use 0. Note: it doesn't matter if a column is hidden (ie. has width 0). It still 'counts'.
The row number is Form.CurrentRecord - 1. Remember that the forms recordset index starts at 1, but the index of our listbox starts at 0; hence the - 1.
So why do we need a duplicate of this new row in the AfterUpdate event? Try and comment it out to see what happens if we don't put it in. It's has to do with the Form_Current event firing after we use the listbox.
I fixed this issue with a union clause in the SQL lookup code. The UNION ALL clause and the following union on the table used in the other part had allowed all the records to be used.