Domanda

I'm just learning how to do VBA in excell and I need some help. I have been searching this site but have not found an example that I could tweak that solves my needs (or at least what I could understand). I am trying to make a button that archives data. I have 2 sheets where one is for user input and the other is the archived location. I would like to have excell take the value in column C and past it in the matching location in sheet 2 based on the valves of sheet 1's values in column A and B.

    Sheet 1

A _______ B______C (user inputed value)

Item 1 ___Date ___ 5

Item 2 ___Date ___ 8

Item 3 ___Date ___ 2

     Sheet 2 (archive sheet)

A ______ B _________ C _______ D

_______Item 1 ___ Item 2 ____ Item 3

Date

Date

Date

I was using a method of just copying the sheet 1 data on a 3rd sheet and running a vlookup but if the user archived the same date twice it would only get the value of the most recent archive. Im not sure how loops work but what I found on other peoples requests I think something like that may be helpful.

Any insight would be most appreciated.

È stato utile?

Soluzione

If you do not know how loops work, you must learn the basics of Excel VBA. You cannot hope to stitch together bits of code gathered from the internet without some understanding of VBA.

Search for "Excel VBA Tutorial". You will get many hits of which many will be for free online tutorials. These tutorials differ in approach so try a few to see which best matches your learning style. Alternatively, visit a good bookshop or library where you will find a selection of Excel VBA Primers. I suggest a library so you can take a few books home for a try before purchasing your favourite.

There are many holes in your specification. Perhaps you have a complete specification which you have not documented here. If you have a complete specification, please do not add it to your question. For sites like this, you need small focused questions.

Two design questions I have spotted are:

  • Why fill the Date column with =TODAY()? If the archive macro is not run at the end of the day, Excel will have changed the date when the macro is run the next day. Either fill the column with the date value or use the nearest VBA equivalent function which is Now().

  • You imply the user might enter a count for Item A and then enter another count later in the day. The archive sheet is to hold the total of those two counts. How is this handled? You could have two or more rows for Item A. The user could run the archive macro before entering a new value in the Item A row. You could use a Worksheet Change event to automatically archive the value after the user has entered it.

You need to fully specify what the macro is going to do and how it is going to be used before trying to code it. Below I have provided two alternative macros that achieve what I believe is the first step of your requirement: locate valid rows in the data entry worksheet and extract the values ready for achiving.

I suggest you study basic Excel VBA first. That should give you enough knowledge to understand my macros even though the second macro uses non-basic statements. Come back with questions as necessary but please run and try to understand the macros before asking these questions.

Demo1

I created a worksheet "Data Entry" and filled it with data that matches my understanding of your worksheet "Sheet1". Please do not use the default worksheet names because it gets very confusing. Replace my name with whatever you choose.

The macro Demo1 outputs the values from valid rows to the Immediate Window. Writing to the Immediate Window is a convenient way of testing small blocks of code as they are written.

I have documented what the code does but not the VBA statements. Once you know a statement exists, it is usually easy to look it up.

Option Explicit
Sub Demo1()

  Dim CountCrnt As Long
  Dim DateCrnt As Date
  Dim ItemCrnt As String
  Dim RowCrnt As Long
  Dim RowLast As Long

  With Worksheets("Data Entry")

    ' This sets RowLast to the last used row in column "C" or sets it to 1 if no
    ' row is used.  It is the VBA equivalent of positioning the cursor to the
    ' bottom of column C and clicking Ctrl+Up
    RowLast = .Cells(Rows.Count, "C").End(xlUp).Row

    ' I have assumed the first data row is 2
    For RowCrnt = 2 To RowLast
      ' I have allowed for column C being empty.  I assume such rows are
      ' to be ignored.  I also ignore rows with invalid values in columns
      ' B or C.
      If .Cells(RowCrnt, "C").Value <> "" And _
         IsNumeric(.Cells(RowCrnt, "C").Value) And _
         IsDate(.Cells(RowCrnt, "B").Value) Then
        ' Extract the validated values to variables ready for the next stage
        ' of processing.
        ItemCrnt = .Cells(RowCrnt, "A").Value
        DateCrnt = .Cells(RowCrnt, "B").Value
        CountCrnt = .Cells(RowCrnt, "C").Value
        ' Output row values to Immediate Window
        Debug.Print RowCrnt & "  " & ItemCrnt & "  " & _
                    Format(DateCrnt, "dmmmyy") & "  " & CountCrnt
      End If
    Next

  End With

End Sub

Demo2

Macro Demo2 achieves the same as macro Demo1 but in a different way.

Demo1 accessed the cells within the worksheet individually. Demo2 copies the entire worksheet to a Variant which can then be accessed as a 2D array. This is much faster that individual cell access and is usually more convenient if you only want the cell values.

Demo1 output values to the Immediate Window. This is very convenient for small volumes of output but early lines will be lost for larger volumes. Demo2 creates a file within the same folder as the workbook and writes the output to that file so nothing will be lost.

Sub Demo2()

  Dim CountCrnt As Long
  Dim DateCrnt As Date
  Dim FileOutNum As Long
  Dim ItemCrnt As String
  Dim RowCrnt As Long
  Dim RowLast As Long
  Dim SheetValue As Variant

  FileOutNum = FreeFile

  Open ActiveWorkbook.Path & "\Demo2.txt" For Output As #FileOutNum

  With Worksheets("Data Entry")
    ' This statement converts Variant SheetValue to an appropriately sized
    ' two-dimensional array and copies the values from the entire used
    ' range of the worksheet to it.
    SheetValue = .UsedRange.Value
    ' Standard practice for 2D arrays is to have the first dimension for
    ' columns and the second for rows.  For arrays copied from or to
    ' worksheets, the first dimension is for rows and the second is for
    ' columns.  This can be confusing but means that array elements are
    ' accessed as SheetValue(Row, Column) which matches Cells(Row, Column).
    ' Note that the lower bounds for both dimensions are always one. If the
    ' range copied from the worksheet starts at Cell A1, row and column
    ' numbers for the array will match those of the worksheet.

  End With

  For RowCrnt = 2 To UBound(SheetValue, 1)

    ' I have allowed for column 3 (= "C") being empty.  I assume such rows
    ' are to be ignored.  I also ignore rows with invalid values in columns
    ' 2 (= "B") or 3.
    If SheetValue(RowCrnt, 3) <> "" And _
       IsNumeric(SheetValue(RowCrnt, 3)) And _
       IsDate(SheetValue(RowCrnt, 2)) Then
      ItemCrnt = SheetValue(RowCrnt, 1)
      DateCrnt = SheetValue(RowCrnt, 2)
      CountCrnt = SheetValue(RowCrnt, 3)
      ' Output row values to file
      Print #FileOutNum, RowCrnt & "  " & ItemCrnt & "  " & _
                         Format(DateCrnt, "dmmmyy") & "  " & CountCrnt
    End If
  Next

  Close #FileOutNum

End Sub

Edit New section in response to supplementary question.

As you have discovered there is no way of "printing" to a worksheet but it is easy to write to individual cells. I have used a diagnostic worksheet but I normally consider this technique more trouble than it is worth. Output to a file is easier to add and easier to delete and it does not interfere with the code.

The code below is in the correct order but I have added explanations between blocks.

Dim RowDiagCrnt As Long

The above statement is not within a subroutine which makes is a gloabl variable that can be accessed from any routine. If there are several routines that need to output diagnostic information, it is easier to use a global variable for the row number than pass it as a parameter from the parent routine.

I have a system for naming variables, "Row" means this is a row. "Diag" identifies the worksheet". "Crnt" identifies this as the current row number. In Demo1, I had RowCrnt because I only had one worksheet. You may not like my system. Fine, develop your own. Having a system means I can look at a macro I wrote years ago and know what all the variables are. This makes maintenance much, much easier.

Sub Demo3()

  Dim CountCrnt As Long
  Dim DateCrnt As Date
  Dim ItemCrnt As String
  Dim RowDiagCrnt As Long
  Dim RowEntryCrnt As Long
  Dim RowEntryLast As Long
  Dim ValidRow As Boolean
  Dim WkshtDiag As Worksheet
  Dim WkshtEntry As Worksheet

I now have two worksheets and I will have to switch between them. I do not like multiple uses of Worksheets("Xxxxx") because I might change "Xxxxx". A reference avoids multiple uses of the name and is faster.

  Set WkshtEntry = Worksheets("Data Entry")
  Set WkshtDiag = Worksheets("Diagnostics")

  ' Delete existing contents of diagnostic worksheet and create header row
  With WkshtDiag
    .Cells.EntireRow.Delete
    .Cells(1, "A").Value = "Row"
    .Cells(1, "B").Value = "Item"
    .Cells(1, "C").Value = "Date"
    .Cells(1, "D").Value = "Count"
  End With

  RowDiagCrnt = 2

  With WkshtEntry
    RowEntryLast = .Cells(Rows.Count, "C").End(xlUp).Row
  End With

  For RowEntryCrnt = 2 To RowEntryLast

I must keep the access to the two worksheet separate if I want to use With statements. I have used a boolean to handle this problem.

    With WkshtEntry
      If .Cells(RowEntryCrnt, "C").Value <> "" And _
         IsNumeric(.Cells(RowEntryCrnt, "C").Value) And _
         IsDate(.Cells(RowEntryCrnt, "B").Value) Then
        ItemCrnt = .Cells(RowEntryCrnt, "A").Value
        DateCrnt = .Cells(RowEntryCrnt, "B").Value
        CountCrnt = .Cells(RowEntryCrnt, "C").Value
        ValidRow = True
      Else
        ValidRow = False
      End If
    End With

    If ValidRow Then
      With WkshtDiag
        ' Output row values to Diagnostic worksheet
        .Cells(RowDiagCrnt, "A").Value = RowEntryCrnt
        .Cells(RowDiagCrnt, "B").Value = ItemCrnt
        With .Cells(RowDiagCrnt, "C")
          .Value = DateCrnt
          .NumberFormat = "dmmmyy"
        End With
        .Cells(RowDiagCrnt, "D").Value = CountCrnt
        RowDiagCrnt = RowDiagCrnt + 1
      End With
    End If

  Next

  ' Set columns to appropriate width for contents
  With WkshtDiag
    .Columns.AutoFit
  End With

End Sub

I hope you can see why the reasons for all the changes I made to Demo1 to create Demo3. Having a second worksheet that is not required for the final solution adds complexity that I normally prefer to avoid.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top