I've created a form within Access which uses a cross-tab query as its data source.
The column headings for the query are 1, 2, 3, 4 and 5 representing week numbers.
The values display items such as 3/3 = 100.00% or 0/13 = 0.00% or 3/14 = 21.00%.
I've added conditional formatting to the text boxes on the form.
Expression Is Right([2],7)="100.00%" works and displays the figure in bold red when the percentage is 100.
Expression is Val(Right([2],7))=100 also works - converting the text value to a numeric value.
The problem I'm having is that I'm not always looking for 100% - it depends on the value within a table. What I'm trying to do is
Val(Right([2],7))=(SELECT ParamValue*100 FROM tbl_System WHERE Param='SampleSize') - this doesn't work.
Neither does:
Eval(Val(Right([2],7))=(SELECT ParamValue*100 FROM tbl_System WHERE Param='SampleSize'))
or
Val(Right([2],7))=EVAL(SELECT ParamValue*100 FROM tbl_System WHERE Param='SampleSize')
or
Val(Right([2],7))=DLookUp("ParamValue","tbl_System","Param= 'SampleSize'")*100
or
Val(Right([2],7))=Eval(DLookUp("ParamValue","tbl_System","Param= 'SampleSize'")*100)
The SQL for the cross-tab query is:
TRANSFORM NZ(Sum(Abs([Include])),0) & "/" & NZ(Count(*),0) & " = " &
FormatPercent(NZ(Round(Sum(Abs(Include))/Count(*),2),0),2)
SELECT tbl_TMP_PrimaryDataSelection.TeamMember
FROM tbl_TMP_PrimaryDataSelection
GROUP BY tbl_TMP_PrimaryDataSelection.TeamMember
PIVOT tbl_TMP_PrimaryDataSelection.WeekNum In (1,2,3,4,5)
I don't think you can use a function in there, be it system or user-defined.
But you can define the FormatCondition dynamically at runtime, like this:
Dim txtFld As TextBox
Dim objFrc As FormatCondition
Dim strExpr As String
Set txtFld = Me!myTextBox
' Remove existing FormatConditions
txtFld.FormatConditions.Delete
' The dynamic expression
strExpr = "Val(Right([2],7))=" & DLookUp("ParamValue","tbl_System","Param='SampleSize'")*100
' Assign a new FormatCondition to text box
Set objFrc = txtFld.FormatConditions.Add(acExpression, , strExpr)
' Set the format
objFrc.ForeColor = &HFF0000
This example simply removes and recreates all FormatConditions. If you have a fixed number of conditions, you can also use the FormatCondition.Modify method (see online help).
Edit:
The final code I have used executes on the Form_Load event and adds a format to each of the five weekly text boxes:
Private Sub Form_Load()
Dim aTxtBox(1 To 5) As TextBox
Dim x As Long
Dim oFrc As FormatCondition
Dim sExpr As String
With Me
Set aTxtBox(1) = .Wk1
Set aTxtBox(2) = .Wk2
Set aTxtBox(3) = .Wk3
Set aTxtBox(4) = .Wk4
Set aTxtBox(5) = .Wk5
For x = 1 To 5
aTxtBox(x).FormatConditions.Delete
sExpr = "Val(Right([" & x & "],7))>=" & DLookup("ParamValue", "tbl_System", "Param='SampleSize'") * 100
Set oFrc = aTxtBox(x).FormatConditions.Add(acExpression, , sExpr)
oFrc.ForeColor = RGB(255, 0, 0)
Next x
End With
End Sub
Edit 2
Yes, defining FormatConditions via VBA is especially useful when dealing with multiple controls in a loop. You can do this in Design View too and save the FormatConditions permanently, simply to avoid going through the FormatConditions dialogs one by one. Or if the customer later decides that he'd rather have a different color. :)
Note: You could use Set aTxtBox(x) = Me("Wk" & x) in the loop. But actually you don't need multiple TextBox variables, you can simply re-use it.
Related
I have a sheet in libreoffice Calc which has an Id Column with incremental value from 1 to N.
I need to create a Macro in VBA (linked to a button i will create later) where i can select the last ID (which is the MAX id also) and delete the entire row relating to this ID.
i tried this so far
Sub suppression
dim maxId as Integer
my_range = ThisComponent.Sheets(0).getCellRangebyName("B19:B1048576")
maxId = Application.WorksheetFunction.Max(range("Dépôts!B19:B1048576"))
MsgBox maxId
End Sub
Thanks a lot for your help.
In libreoffice BASIC you first need to get the data array of the cell range. This is an array of arrays each representing a row of the cell range. It is indexed from zero irrespective of the location of the cell range within the sheet. Because your cell range is one column wide, each member array has only one member, which is at index zero.
As Jim K says, 'Application.WorksheetFunction' is from VBA. It is possible to use worksheet functions in LibreOffice BASIC, but these act on ordinary arrays rather than cell arrays, and the MAX function takes a one-dimensional array so it would be necessary to first reshape the data array using a loop. Furthermore, if you want to delete the row corresponding to the maximum value you are then faced with the problem of finding the index of that row using only the value itself.
It is much simpler to find the index by looping over the data array as shown in the snippet below.
Also, rather than traversing over a million rows, it would save computational effort to obtain the last used row of the spreadsheet via the BASIC function 'GetLastUsedRow(oSheet as Object)', which is supplied with LibreOffice. This is located in the 'Tools' library in 'LibreOffice Macros & Dialogs'. To use it you have to put the statement: 'Globalscope.BasicLibraries.LoadLibrary("Tools")' somewhere before you call the function.
To delete the identified row, get the XTableRows interface of the spreadsheet and call its removeByIndex() function.
The following snippet assumes that the header row of your table is in row 18 of the sheet, as suggested by your example code, which is in row 17 when numbered from zero.
Sub suppression()
' Specify the position of the index range
''''''''''''''''''''''''''''''''''''
Dim nIndexColumn As Long '
nIndexColumn = 1 '
'
Dim nHeaderRow As Long '
nHeaderRow = 17 '
'
''''''''''''''''''''''''''''''''''''
Dim oSheet as Object
oSheet = ThisComponent.getSheets().getByIndex(0)
' Instead of .getCellRangebyName("B19:B1048576") use:
Globalscope.BasicLibraries.LoadLibrary("Tools")
Dim nLastUsedRow As Long
nLastUsedRow = GetLastUsedRow(oSheet)
Dim oCellRange As Object
' Left Top Right Bottom
oCellRange = oSheet.getCellRangeByPosition(nIndexColumn, nHeaderRow, nIndexColumn, nLastUsedRow)
' getDataArray() returns an array of arrays, each repressenting a row.
' It is indexed from zero, irrespective of where oCellRange is located
' in the sheet
Dim data() as Variant
data = oCellRange.getDataArray()
Dim max as Double
max = data(1)(0)
' First ID number is in row 1 (row 0 contains the header).
Dim rowOfMaxInArray As Long
rowOfMaxInArray = 1
Dim i As Long, x As Double
For i = 2 To UBound(data)
x = data(i)(0)
If x > max Then
max = x
rowOfMaxInArray = i
End If
Next i
' if nHeaderRow = 0, i.e. the first row in the sheet, you could save a
' couple of lines by leaving the next statement out
Dim rowOfMaxInSheet As long
rowOfMaxInSheet = rowOfMaxInArray + nHeaderRow
oSheet.getRows().removeByIndex(rowOfMaxInSheet, 1)
End Sub
I have a SET-field in Word 2007. After the set-field there could be everything (text,bookmark, SET field,...). I want to add a text (e.g. "exampletext") in between.
After this I want to delete this inserted text (but I don't want to search through the whole document).
Is there a method?
Trial 1 (it inserts it in the field - and not after the field):
' xStartReturn is a field
Dim myExampletext As WordApp.Range = objDoc.Range(xStartReturn.Code.End, xStartReturn.Code.End )
myExampletext.Text = "exampletext"
Trial 2 (leads to the problem that I don't get the Range-field to delete the exampletext afterwards):
xEndeReturn.insertAfter("exampletext")
Trial 3:
'xStartReturn.Code.End + 1 doesn't work.. but I found out that the "}"-Sign in the setField is +20 after xStartReturn.Code.End. Theoretical this should work - but there could be e.g. also paragraph afterwards.
'-> I can automatically check that there is a paragraph - but why is the exampletext added **after** the paragraph?
Dim example As WordApp.Range = objDoc.Range(xStartReturn.Code.End + 20, xStartReturn.Code.End + 20)
example.Text = "exampletext"
Dim later As WordApp.Range = objBasisvorlage_.Range(objXStartReturn.Code.End + 20, objXStartReturn.Code.End + 20 + "SDFSD".Length) 'this is wrong?!
later.Delete()
The following works for me. Since you didn't give us a minimum code with which to reproduce the problem I don't know how relevant the framework is that I used. But you should be able to follow the steps.
Watch what I do with r_f_Code (field code range). You can ignore/remove r_f_Result as I had that in for reference and debugging purposes.
Collapsing the field code range to its end-point leaves the range just within the field braces. Moving the starting point one character to the right puts it just outside the braces, but before anything else. (Note: I tested with two immediately adjacent SET fields.)
My code then enters some text and bookmarks it. That's the only way you do what you ask if what follows the SET field can be "anything". Although I suppose you could insert a Content Control - that would be uniquely identifiable if you go about it correctly...
Sub PositionAfterFieldCode()
Dim f As word.Field
Dim r_f_Code As word.Range, r_f_Result As word.Range
For Each f In ActiveDocument.Fields
If f.Type = wdFieldSet Then
Set r_f_Code = f.code
Set r_f_Result = f.result
'Debug.Print Len(r_f_Code), r_f_Code.Text, Len(r_f_Result), r_f_Result.Text
r_f_Code.Collapse wdCollapseEnd
r_f_Code.MoveStart wdCharacter, 1
'r_f_Code.Select
r_f_Code.Text = "abc"
r_f_Code.Bookmarks.Add "AfterSet", r_f_Code
Exit For
End If
Next
End Sub
This Macro is used to cut, insert and delete a cell range section of a workbook.
The problem I was trying to solve and gave up with the lack of response in another thread is why copying multiple non-adjacent rows to the MS clipboard often loses their row line-breaks when pasting.
E.g. Since trying to paste 3 non-adjacent rows into row 10, 11 and 12, often puts all 3 rows into row 10 with one row in fields A10-P10, the next row in Q10-AF10 and the last row into AG10-AV10...
I edited the Macro below to fix this mistake when this happens.
So, for example, I can now highlight row 10 and run the macro to cut/insert the fields Q10-AF10 to A11-P11 and delete/shift left the blank fields now in Q10-AF10.
I'm hoping for help to loop this process until there's no data outside Column A-P. In this case, no data outside cell P10.
Sub FixAllOnLine1OneRowAtATimeInsertToNextRow()
Application.ScreenUpdating = False
Dim copySheet As Worksheet
Dim pasteSheet As Worksheet
Set copySheet = ActiveSheet
Set pasteSheet = ActiveSheet
copySheet.Range("Q" & ActiveCell.Row & ":AF" & ActiveCell.Row).Copy
Range("Q" & ActiveCell.Row & ":AF" & ActiveCell.Row).Offset(1).Select
pasteSheet.Cells(ActiveCell.Row, 1).End(xlUp).Offset(1, 0).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Application.ScreenUpdating = True
Columns("Q:AF").Select
Selection.Delete Shift:=xlToLeft
End Sub
Ok, I made some headway. I just have one super easy issue and then I need to loop it.
The first issue is that it cuts Column Q:AF correct of the row I've highlighted and shifts the entire Column Q:AF to the left, but it INSERTS the cut cells into the fixed range, A2:P2. I want to INSERT the cut cells down ONE row from my selection. I KNOW this is a couple characters in the Offset, I just can't get it.
Then, once it's working properly...say I highlight row 10, it cuts Q10:AF10 and instead INSERTS the cells into A11:P11 and shifts "Q:AF" to the left, then I need to figure out how to get it to loop until there's no more data to right of Column P. When this problem occurs pasting multiple rows from the clipboard all into the first row losing the row line-breaks, it's always quite a few rows.
Any ideas?
Thanks so much!
Mark
Sub FixAllOnLine1OneRowAtATimeInsertToNextRow()
Dim ws As Worksheet
Dim lNextRow As Long
Application.ScreenUpdating = False
Set ws = ActiveSheet
ws.Range("Q" & ActiveCell.Row & ":AF" & ActiveCell.Row).Copy 'Copy the row of the selected cell from Q:AF
ws.Range("Q" & ActiveCell.Row & ":AF" & ActiveCell.Row).Offset(1).Select 'Select the cells you have just copied. Not needed
ws.Cells(ActiveCell.Row, 1).End(xlUp).Offset(1, 0).Insert xlShiftDown ' Paste the copied values in to column "A" on next row?
'lNextRow = ws.Range("A" & Rows.count).End(xlUp).Row + 1 'Get Next Row number
'Range("A" & lNextRow).PasteSpecial xlPasteValues
Application.CutCopyMode = False
Range("Q:AF").Delete Shift:=xlToLeft
'Columns("Q:AF").Select
'Selection.Delete Shift:=xlToLeft
Application.ScreenUpdating = True
ActiveCell.Offset(RowOffset:=-1, columnOffset:=0).Activate 'Added to move active cell up one row to run it again for multiple groups to apply fix.
End Sub
Here's a solution in another direction just in case someone from the engines needs it...
Sub ReduceNoOfColumns()
Dim iRow As Integer 'Row to be manipulated
Dim iRowToPasteTo 'Row number to paste the copied cells
Dim iCurCol As Integer 'Current Column number of first cell with a value to cut
Dim NoOfCols As Integer 'integer to hold max number of columns
Dim sAddress As String
iRow = ActiveCell.Row
iRowToPasteTo = iRow + 1
NoOfCols = 16 'Set this number to the total number of columns you wish to have (in your case 16)
iCurCol = NoOfCols + 1
Do Until Cells(iRow, iCurCol).Value = "" 'Keep looping until we get to an empty column
sAddress = ColNoToLetter(iCurCol) & iRow & ":" & ColNoToLetter(iCurCol + NoOfCols - 1) & iRow
Rows(iRowToPasteTo & ":" & iRowToPasteTo).Insert Shift:=xlDown
Range(sAddress).Copy
Range("A" & iRowToPasteTo).PasteSpecial xlPasteAll
Range(sAddress).Clear
iCurCol = iCurCol + NoOfCols
iRowToPasteTo = iRowToPasteTo + 1
Loop
End Sub
Function ColNoToLetter(iCol As Integer) As String
Dim vArr
vArr = Split(Cells(1, iCol).Address(True, False), "$")
ColNoToLetter = vArr(0)
End Function
I created a query and a form in Microsoft Access 2010. The form, named TEST, looks as follows:
Field1 Field2
a 200
b 400
In VBA I tried to access the different fields in the form:
Form_TEST.Field1....
I want to save the values 200 and 400 in an integer variable (Dim a As Integer) and print it using MsgBox. How can i achieve that??
You can use the Me as the open form and assign the variable if you know the name of the text box.
Dim intValue as Integer
'If text box name = TextBox1
intValue = Me.TextBox1.Value
I'll try to help you.
I understood you created the form with the wizard putting the 2 fields on the form.
What is not clear is the View that you are using.
Well, your form can be displayed in different ways:
- Single form
- Continuous forms
- Datasheet
This is defined by the Default View property.
To see the properties of you form press F4 to see properties and select "Form" as the object that you want to see.
If your form is Single Form or Continuous form you can access the two fields you put on it simply addressing them.
Click on the controls you put on the form and press F4 to see the control name.
CASE 1 - SINGLE FORM VIEW
Let's assume that your controls are named Text1 (200) and Text2 (400) and for convenience your form is a single form.
So you can refer to values in the 2 controls writing
Dim intText1 As Integer, intText2 As Integer
intText1 = Me.Text1.Value
intText2 = Me.Text2.Value
The .Value property is not mandatory cause it's the default property.
At this point you can print out intText1,2 with a MsgBox
MsgBox "Text1 = " & CStr(intText1)+ " - Text2 = " & CStr(intText2)
This will show Text1 = 200 - Text2 = 400
CASE 2 - CONTINUOUS FORMS VIEW
Let's now assume that your view is Continuous form.
So the field that contains 200 and 400 is just one but each record (row) is a form repeated as many times as the number of records.
In this case to access all the records and store them to an array you can use this in the Form_Load event (you can access it by the Control Properties Window - F4)
Option Explicit
Option Base 1 ' Set the base index for vectors to 1
Dim rst as DAO.Recordset ' Define a recordset to allocate all query records
Dim Values as Variant ' Define a variant to allocate all the values
set rst = me.RecordsetClone ' Copy all records in rst
rst.MoveLast ' Go to last record
intNumRecords = rst.RecordCount ' Count records
rst.MoveFirst ' Go back to recordset beginning
ReDim Values(intNumRecords) ' Resize Values to allocate all values
i = 1
Do While Not rst.EOF ' Cycle over all records
Values(i) = rst!FieldName ' FieldName is the name of the field of
' the query that stores 200 and 400
i = i + 1 ' Move to next array element
rst.MoveNext ' Move to next record
Loop
rst.Close ' Close recordset
set rst = Nothing ' Release memory allocated to rst
for i = 1 To intNumRecords ' Show easch value as message box
MsgBox Values(i)
next i
NOTES
Please NOte that this solution works if you have less than 32767 records to show (the maximum integer with sign that you can store).
The msgbox obliges you to press OK at each value. It's not so comfortable.
Please tell me if it's what you were looking for.
Bye,
Wiz
Something that I would assume comes up a lot...
I'd like to know if there's a way to, in Access' Conditional Formatting, format all blank fields. In my case, all fields generally need to be entered, but not in all cases. So, instead of writing a bunch of conditional code to restrict the user to writing it in there, I just want some red backgrounds in my fields as a reminder "hey, there's nothing in here.. sure that's what you wanted?"It's on a tablet so Message Boxes would be annoying. So conditional formatting it is. I know you can have "Is Null([Field]) but that requires me to go through my 20+ forms on 30+ fields and ensure proper field names etc, then type the condition for them individually. Is there a way I can simply multi-select my fields, do a conditional format on Multiple, and use maybe "Is Equal To: NULL"?
I've tried "equal to: Null" and it doesn't work.. nor does "equal to: "" " (using the Access constants). Ideas why? Or how I can get around this? Also, it's only necessary for non-touched fields, so if the user starts to type then deletes back to blank, I don't care; it can stay unformatted or go back to red, so if there's a better way to do this I'm all eyes.
EDIT: I've started doing some VBA code which I will paste into all my forms:
Private Sub Form_Load()
Dim ctl As Control
Dim reqCol As Long
Dim focusCol As Long
Dim doneCol As Long
Dim format As FormatCondition
reqCol = RGB(246, 180, 180)
focusCol = RGB(252, 249, 238)
doneCol = RGB(255, 255, 255)
For Each ctl In Me.Controls
With ctl
Me.Controls(ctl.Name).FormatConditions.Delete 'Delete the existing conditions.
Me.Controls(ctl.Name).BackColor = doneCol 'Set the background color to the done color.
Select Case .ControlType
Case acTextBox
'Create the format objects.
format = Me.Controls(ctl.Name).FormatConditions.Add(acFieldValue, acEqual, "")
format = Me.Controls(ctl.Name).FormatConditions.Add(acFieldHasFocus)
'Format the filled in boxes (ie set back to red)
With Me.Controls(ctl.Name).FormatConditions(0)
.BackColor = reqCol
.Enabled = True
End With
'Format the current field color (ie set to beige)
With Me.Controls(ctl.Name).FormatConditions(1)
.BackColor = focusCol
.Enabled = True
End With
End Select
End With
Next ctl
End Sub
Problem is that FormatConditions.Add(acFieldValue, acEqual, "") doesn't work for the same reason... how do I get around this? Seeing as VBA and the built-in conditions are both flawed, seems like a bug. Or I'm missing something right in front of me..
In Access 2016 I was unable to find the default formatting option that is the solution provided by #SeanC. Instead I found that to get my Combo Box to format properly I had to use an Expression with ISNULL.
Set default format to the way to want zero length data to appear.
use
Field Value Is greater than ''
for the conditional formatting and set that format to how it should appear with text in the field.
You can select multiple fields with Shift+click in design view to select all the appropriate fields that this needs to be applied to
Solved. Put this in my forms (might look into making it a module; new to this, not sure how yet)
Private Sub Form_Load()
On Error Resume Next
Dim ctl As Control
Dim reqCol As Long
Dim focusCol As Long
Dim doneCol As Long
Dim format As FormatCondition
Dim expr As String
reqCol = RGB(246, 180, 180)
focusCol = RGB(252, 249, 238)
doneCol = RGB(255, 255, 255)
For Each ctl In Me.Controls
With ctl
'Delete the existing formatting
Me.Controls(ctl.Name).FormatConditions.Delete
Me.Controls(ctl.Name).BackColor = doneCol
Select Case .ControlType
Case acTextBox
expr = "IsNull(" & ctl.Name & ") = True"
'Create the format objects.
format = Me.Controls(ctl.Name).FormatConditions.Add(acFieldHasFocus)
format = Me.Controls(ctl.Name).FormatConditions.Add(acExpression, , expr)
'Format the filled in boxes (ie set back to focus color)
With Me.Controls(ctl.Name).FormatConditions(0)
.BackColor = focusCol
.Enabled = True
End With
'Format the current field color (ie set to required color)
With Me.Controls(ctl.Name).FormatConditions(1)
.BackColor = reqCol
.Enabled = True
End With
End Select
End With
Next ctl
End Sub
The trick was how to enter it into FormatConditions.Add(...). Works exactly how I'd like it to now.