Extra space every 10 lines on a Microsoft Access report - ms-access

I'd like to add some extra space every 10 lines on each report page for readability.
I've tried counting lines and changing the height of the detail section in the Detail_Format event routine but that didn't seem to do anything.
Then I tried using a tall dummy field with just a space char that was made visible every 10 lines (along with the proper CanShrink settings), and the extra space was visible but not on the correct lines. Instrumentation showed that the event routine was calculating properly but the effect seemed to be out of sync.
But there must be a straightforward way to do this, right? Any suggestions?

Such a result is only possible at print layout as far as I know.
That's how I did it:
The sample counts the printed rows for each page and uses the Print event of the Detail section to add an empty row if necessary by setting PrintSection and NextRecord properties of the report.
To assure a resetting of the counter for each page and prevent that the first row of a page will be blank the Format event of the PageHeaderSection is used.
Use the constant INSERT_BLANK_ROW_EVERY to set when a blank row should be inserted.
Const INSERT_BLANK_ROW_EVERY As Integer = 10
Dim insertBlankRowNext As Integer
Dim pageLineCount As Integer
Private Sub Detail_Print(Cancel As Integer, PrintCount As Integer)
If PrintCount = 1 Then pageLineCount = pageLineCount + 1
Me.PrintSection = Not insertBlankRowNext
Me.NextRecord = Not insertBlankRowNext
insertBlankRowNext = (pageLineCount Mod INSERT_BLANK_ROW_EVERY = 0)
End Sub
Private Sub PageHeaderSection_Format(Cancel As Integer, FormatCount As Integer)
pageLineCount = 0
insertBlankRowNext = False
End Sub
Regarding the PrintCount parameter Microsoft writes:
Microsoft Access increments the PrintCount property each time the OnPrint property setting is evaluated for the current section. As the next section is printed, Access resets the PrintCount property to 0.
See details here: Report.PrintCount property

You can also add dummy fake records that are blank.
Assuming your report is based on query e.g. Query1, instead of your using it as RecordSource for the report, you could use it as source for a table (Make Table) and create a table that holds the information you want but with the extra records (lines) for readability.
Just after the "dumping" of the Query1 to table..you load it on a RecordSet ...you iterate the records and according to your needs you .AddNew records.

Based on the information in UnhandledException's answer, this is how I did it, and it's it's actually very easy to do.
Step 1: Add a text box of the desired height on the detail line. I think CanShrink must be set for both the text box and the detail line.
Step 2: Add these to your event handling:
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
LineFormatEvents = LineFormatEvents + 1
Me.txtExtraSpace.Visible = (LineFormatEvents Mod 10 = 0)
End Sub
Private Sub PageHeaderSection_Paint()
LineFormatEvents = 0
End Sub
Note that I'm using Access 2016; I don't know how many versions back this will work.

Related

Access VBA - Turn off aggregation when opening form/sub-form

What I'm trying to do is, whenever a user opens a form (and the sub-form that opens by default), I want to search through all the columns (controls?) on the form, check to see if they are currently set to aggregate (sum, count etc.) with Access' built-in Totals row, and if so, set them to not aggregate.
The reason for this is there are several millions records that are stored, so when someone queries it down to 3-4 and turns on Sum, then closes it, when the next person opens it, it tries to sum millions of numbers and freezes up. The form displays the queried results from a table which is populated via SQL (I think, if that sentence makes sense). Here's what I have so far:
Private Sub Form_Load()
'this form_load is in the UserApxSub sub-form, for reference
Call De_Aggregate
End Sub
Private Sub De_Aggregate()
Dim frm As Form, con As Control
Set frm = Forms!UserAPX!UserApxSub.Form!
For Each con In frm.Controls
If con.ControlType = acTextBox Then
If con.Properties("AggregateType").Value <> -1 Then
'crashes on following line
con.Properties("AggregateType").Value = -1
End If
End If
Next con
End Sub
I have not so much experience in Access VBA (usually work in Excel VBA) so please forgive me if I'm entirely off the mark here. The command con.Properties("AggregateType").Value = -1 doesn't throw an error, but Access just straight-up crashes when reaching that line specifically.
I've tried a number of variations in the syntax with no success, and I've also tried looping through other elements of the file (tabledefs, querydefs, recordsets, etc.) as well to see if I'm trying to change the wrong value, but the controls on this subform are the only things in the entire .mdb file that results when I search for elements with the AggregateType property.
I switched out the line that errors with Debug.Print con.Name & " - " & con.Properties("AggregateType").Value and I can check, have nothing return anything other than -1, turn on aggregation in some column manually, and have it return the correct result (0 for sum for example), so I think I'm looking in the right place, just missing some key factor.
I've been working on this for a couple weeks with no success. Any way to fix what I have or point me toward the right direction would be greatly appreciated!
This is not necessarily the answer but I don't have enough reputation
to give a "comment"...
I tried your scenario and verified can change the property value as you are however I did not iterate through all controls and simply used an onDoubleClick event on a column to simulate.
I would suggest trying to fire your sub with Form_Open or Form_Current to see if the property is getting reset after your code has been called for some reason.
UPDATE:
You are referencing the "Subform" Object of your main Form:
Set frm = Forms!UserAPX!UserApxSub.Form!
Try referencing the actual UserApxSub FORM explicitly.
Something like Set frm = Forms!UserApxSub! (assuming UserApxSub is the name of the form)
then stick in the Form_Open of your main form:
Private Sub Form_Open(Cancel As Integer)
'// the following would set a single control only. You can add your loop through all controls
Me!{your control name}.Properties("AggregateType").Value = -1 '// set control in your main form
Form_UserApxSub!{your control name}.Properties("AggregateType").Value = -1 '// set control in your "sub" form
End Sub

Requery only one record in a datasheet view form (MS Access)

I have a rather simple looking problem but it turned out to be more complicated than I thought.
I have a field (column) in my subForm which is a ComboBox.
I have a field (column) in another subForm by which I would like to filter this comboBox.
Basically, the comboBox before filtering has some 600 records, too many to scroll by casual user. I created a simple subForm whose field is linked to a mainForm and this works perfectly (ie. the selected record-field-ID is displayed on mainForm).
Now what I want is that this comboBox is filtered by this record (ie. only showing relevant fields). The problem is that if I simply Requery it with this given filter, the other fields show up blank.
I want this filter to apply only to NEW RECORDS comboBox, not the whole datasheet view.
What I did is:
Private Sub Sekacie_Operacie_GotFocus()
Dim SQL As String
SQL = CurrentDb.QueryDefs("qrySekacie_Operacie").SQL
Me.Sekacie_Operacie.RowSource = Replace(SQL, ";", "") & " WHERE Sekacie_Operacie.Operacia_ID = " & Me.Parent.Form!txtOP_ID
Me.Sekacie_Operacie.Requery
'works kinda as intended
End Sub
Private Sub Form_AfterInsert()
Me.Sekacie_Operacie.RowSource = CurrentDb.QueryDefs("qrySekacie_Operacie").SQL
Me.Refresh
End Sub
And when I select the record in my filter subForm:
Private Sub Form_Current()
Dim SQL As String
SQL = CurrentDb.QueryDefs("qrySekacie_Operacie").SQL
With Me.Parent.Form.subSekacie_Operacie_Modelu
.Form!Sekacie_Operacie.RowSource = SQL
.Form.Refresh
End With
End Sub
However, this workaround still shows "blank" records sometimes (I have to refresh the form by clicking different record) and I find it strange I had to go all the way to do this. Is there no simpler way of accomplishing this?
A couple of immediate things:
No need to refresh the whole sub form, just refresh the control.
Private Sub Form_AfterInsert()
' Me.Refresh ' replace this line with the following line
Me.Sekacie_Operacie.Refresh ' or is it Requery I can't remember
End Sub
The following code can be improved
Private Sub Form_Current()
Dim SQL As String
SQL = CurrentDb.QueryDefs("qrySekacie_Operacie").SQL
With Me.Parent.Form.subSekacie_Operacie_Modelu
.Form!Sekacie_Operacie.RowSource = SQL
.Form.Refresh
End With
End Sub
' by using code like this
Private Sub Form_Current()
Dim SQL As String
SQL = CurrentDb.QueryDefs("qrySekacie_Operacie").SQL
me.Sekacie_Operacie.RowSource = SQL
' I think the above line will cause the combo to refresh
' anyway (in some version of accees it might not,
' but, if needed add the following line
' me.Sekacie_Operacie.refresh
End Sub
The following line has the potential to produce and error
(perhaps when the main form is a new record)
WHERE Sekacie_Operacie.Operacia_ID = " & Me.Parent.Form!txtOP_ID
replace it with
WHERE Sekacie_Operacie.Operacia_ID = " & nz(Me.Parent.Form!txtOP_ID,0)
Note that adding such code will stop the subform from working independently of the main form and will require it to have a parent form.
You could get around this by using:
if not me.parent is nothing then
WHERE Sekacie_Operacie.Operacia_ID = " & nz(Me.Parent.Form!txtOP_ID,0)
end if
About your approach.
I find it a bit weird what you are doing. Consider this:
Say the query qrySekacie_Operacie returns 100 rows when run without any WHERE clause. When you are inserting a record into the subform you want to limit the combo by adding a WHERE clause that uses a value in the parent formMe.Parent.Form!txtOP_ID) , this might limit the number of rows displayed to 10.
It is worth noting that if any of the other rows in the subform contain value other than one of these 10 the combo will display a blank value. I think this is what you are experiencing.
My first question was why not use the on current event of the parent form to change the subform combo box SQL. It appears this is not what you need.
It appears that other values can exist in the dataset of the subform and that you only want certain value to be enterable in this subform but you want the subform to display any values that have been entered.
So that gets interesting. Here's what I would do:
Create a hidden combo control (with no control source) that is only visible when a new record is being entered. When this new combo is shown the other one is hidden.
Set up this new combo with the required SQL
use a before insert event to copy the value form the new combo to he old one.
Easy peezy?
Let me know ho you get on.
One other thing
I like to Ensure a subform encapsultes code that operates on it "private" controls.
As I general rule of thumb, where possible (which is 99% of the time), I like to try and keep subforms so that they will work as forms in their own right. They can be though of as an object, so if they work independently, it is an indication that their code has been encapsulated within their own code module.
So if the form has me.form.parent = nothing then the code behaves differently as it is a independent form.
So rather than the parent form code module having code that sets the SQL of the subforms combo, I would write a public sub in the subforms module (called SetComboSQL) that can be called (with the appropriate parameters. ie OPid ) to set the combo SQL.
To call the procedure in the parent form I would add
Private Sub Form_Current()
Me.MySubformControlName.Form.SetComboSQL(OPid:=Me.txtOP_ID)
End Sub
' note that intellisense won't pick up this proeprty BUT it will work. Use debugger to check it out.
I hope you find this tip useful (as it can be a massive headache saver. THE MOST BASIC OO PRINCIPAL is encapsulation. An object should own all the code that effects it's properties (and controls).

Access: Refer to text box from detail section of subreport from main report

I can successfully refer to the value in a text box in the subreport footer using [subreportName].[Report].[textBoxName]. However, I would like to refer to a text box in the detail section. Using the above just gives the last value it contained - I would like to refer to a specific one (by field, say).
Is this possible, or is there some workaround?
Update: This is what I have so far. fieldA is the name of the text box that I want to use to pick out the correct entry (the correct entry will have "keyString" in this text box), and fieldB contains the value I want to actually store.
In the OnFormat event in the subreport detail section:
Dim varToStore As Double
Private Sub Detail_Format(Cancel As Integer, FormatCount As Integer)
If Me![fieldA] = "keyString" Then
varToStore = Me![fieldB]
End If
End Sub
In a new Module:
Function getVariable(Name As String)
If Name = "varToStore" Then
getVariable = varToStore
Else
getVariable = -1
End If
End Function
In the Control Source of the text box in the main report:
=getVariable("varToStore")
I'm using the general function getVariable since there are a few variables from the detail section that I'm planning to store, so I thought it would be easier to just pass arguments to one function, rather than having a function for each.
If you look at a textbox in the detail section of a continuous form, it is really only one object, even if there are many rows on the screen.
If you change a property, you change all rows. If you read its value, you get the value of the current record.
With the detail section of a report, it's similar. You can't refer to the textbox of any row except the last after it has been printed.
But you can while it is being printed. The OnFormat property of the detail section is most probably the best event to use.
Something like
Private Sub Detailbereich_Format(Cancel As Integer, FormatCount As Integer)
If Me!Keyfield = 4711 Then
' do something specific with the textbox of this specific row
End If
End Sub
To create this sub, open the property sheet of the details section of the subreport.
On the Events tab, select Event procedure for the On Format event. Edit it ("..." button) and you're there.
You can assign the textbox value to a public variable to pass it to the main report. But depending on where/when in the main report you want to use it, it may be too late.
New answer for the Update.
I assume your code doesn't work?
That would be because getVariable("varToStore") is evaluated when the main report is opened, but varToStore is set later, when the subreport is formatted.
So you need a different approach. Don't try to fish your data from the subreport, get it with SQL when the main report is opened. Something like:
Report_Open()
Me.mainTextField.Value = DLookup("fieldB", "SubReportTable", "fieldA = 'keyString'")
mainTextField would be unbound then.
If "keyString" is actually constant, you could even use the DLookup as control source. But I guess it's a variable, so it's easier to construct the DLookup call in VBA.

Conditional Formatting via VBA MS Access Report not working

I am having a trouble while trying to conditionally format the exhibition of records in a report inside a MS Access 2007 form.
I have search the Internet and I have seen lots os fellows stating that it is possible to perform visual changes in a single record via code implementing the method Detail_Paint() for the event Paint of the Detail section in a Report. Those people say that something like this is going to work:
Private Sub Detail_Paint()
val = CStr(Me.someTextBox.Value)
If val = "constraint" Then
Me.lineStrikethrough.BorderStyle = 0
End If
End Sub
The problem is that although the reading statement Me.someTextBox.Value returns the value of each record when the Paint event is thrown, the writing statement Me.lineStrikethrough.BorderStyle = 0 writes the value of the property BorderStyle for every single line in my report, not only for the one respecting the single record whose value I read from someTextBox field.
Can anyone tell me why such is happening? If this is the correct behaviour (although it does not seem right to me), how can I achieve my goal?
Note: lineStrikethrough is used to perform a strikethrough effect over the record in a Report. If there is another way to do that I would be happy to know.
Two things:
1 - Use the Detail's On Print event and not On Paint event.
Private Sub Detail_Print(Cancel As Integer, PrintCount As Integer)
val = CStr(Me.someTextBox.Value)
If val = "constraint" Then
Me.lineStrikethrough.BorderStyle = 0
End If
End Sub
2 - To see the conditional formatting, always open your report in Print Preview and not Report View.
You are really close!! :-) In my testing, it appears that the Detail_Paint() event allows you to change the formatting of controls, but changes will continue in subsequent records until you change/reset them to something else.
In your code sample, you just need to add another line to turn the strikethrough BorderStyle back on when the condition is no longer met.
Private Sub Detail_Paint()
Val = CStr(Me.someTextBox.Value)
If Val = "constraint" Then
Me.lineStrikethrough.BorderStyle = 0
Else
Me.lineStrikethrough.BorderStyle = 1
End If
End Sub
I found this clue on a code sample on MSDN which demonstrates conditional formatting using the Detail_Paint() method.
I found that Select Case to work best in the detail paint over if statements for a continuous form.
'Private Sub Detail_Paint()
Select Case CStr(Me.someTextBox.Value)
Case "constraint"
Me.lineStrikethrough.BorderStyle = 0
Case Else
Me.lineStrikethrough.BorderStyle = 1 'or what the default value is
End Select

Set 'Me.Labelnn.BackColor' with a for nn=... loop in a report with many labels

I have a report which is like an hourly schedule for a whole week. Each cell in the schedule is 1 day for 1 machine. Each column is one of 7 machines. Thus, a row consists of 7 day cells. So far so good, but each day needs 18 rows of text (a Memo field) and - each row needs to be colorized. I've created 18 labels numbered Label33 and up, for each of the 7 columns.
I'm setting their BackColor with a pretty large case statement of 7 cases and 18 x Labelnn.Backcolor=aColor in each case.
However, I'd like to replace this with something that forms the control_name="Label" & nn, and do something like Me.control_name.BackColor=aColor.
Possible? How? (No, considering how flexible VBA-Access is I haven't tried the above yet.)
Cody Gray is correct but this is not helpful if you do not know what a control array is.
The following is Excel VBA but I have done the same with Access VBA but not recently. I think the syntax is the same but I do not guarantee it.
I created a workbook, inserted a form, pulled a selection of controls onto it and then ran the following code:
Option Explicit
Sub TestControls()
Dim InxC As Long
Load UserForm1
With UserForm1
For InxC = 0 To .Controls.Count - 1
Debug.Print .Controls(InxC).Name
Next
End With
End Sub
The output to the immediate window was:
Label1
CommandButton1
ComboBox1
CommandButton2
OptionButton1
You can see that Label1.xxx is exactly the same as .Controls(0).xxx. I name my controls systematically so I can run code like:
With UserForm1
For InxC = 0 To .Controls.Count - 1
If Mid(.Controls(InxC).Name,1,5) = "lblXx" Then
' Code to set properties of all lblXx controls
End If
Next
End With
I use this functionality most when I do not know how many of a particularly type of control I need. I create 10, say, of them which is more than I will ever need and make them all invisible.
At run time, I make those I need visible and set their top and left properties as necessary.
I agree with Cody Gray. Use the Control Arrays.
Here is a link which will help you
http://www.siddharthrout.com/index.php/2018/01/15/vba-control-arrays/
Though Tony has given you a relatively easy way to do it but there are instances where you might add or delete a control or simply mess up on the sequence. Also there are instances when you simply cannot leave the label as Label1. You might be adding some text to it so that it signifies something...
In such a scenario use TypeOf instead of Name
For example
Option Compare Database
Private Sub Command1_Click()
Dim ctl As Control
For Each ctl In Me.Controls
If TypeOf ctl Is Label Then
Debug.Print ctl.Name
End If
Next ctl
End Sub