User Inputs Value Into A Range - Rest Of Cells In

2019-09-01 04:41发布

问题:

I'm trying to have a formula somewhat like a sum function. The only difference is that once it sums up the amounts of 3 cells (or more) it will edit a certain cell to maintain that total value if another cell is edited.

For example:

Column A, B, and C are each 3 and together equal 9:

| A  |  B  |  C |...| Total |
+----+-----+----+...+-------+
| 3  |  3  |  3 |...|   9   |

Columns A and B are edited to equal 2 each, but I still want to maintain the total of 9, so I want column C to automatically change to 5.

this:

| A  |  B  |  C |...| Total |
+----+-----+----+...+-------+
| 2  |  2  |  3 |...|   7   |

should update to:

| A  |  B  |  C |...| Total |
+----+-----+----+...+-------+
| 2  |  2  |  5 |...|   9   |

The reasoning behind this is because it is being sent to multiple parts of the country where only 2 cells will be edited, but I want to maintain a total without having to edit 2k+ rows of data inputs.

I'm open to a VBA option as well. Anyone have any ideas?

回答1:

You can find the problem here.

The example:

Column A, B, and C are each 3 and together equal 9:

| A  |  B  |  C |...| Total |
+----+-----+----+...+-------+
| 3  |  3  |  3 |...|   9   |

Columns A and B are edited to equal 2 each, but I still want to maintain the total of 9, so I want column C to automatically change to 5:

| A  |  B  |  C |...| Total |
+----+-----+----+...+-------+
| 2  |  2  |  3 |...|   7   |

I realized that my code was a bit of a mess, so I broke it into sheet1, module main and a class named CollectionOfGeneratedValues.

The only variables you have to adjust in your code are the masterRange, Columns in rangeToFill and a column in sumTarget to suit your data input.

Quick runthrough:

  • You have to set the masterRange, or the range that you are working with, inside VBA. Inside the spreadsheet you must set the sumtarget for each row of the masterRange.

  • When a value is entered into a cell inside of your masterRange, we find out what row this is and generate a separate range that is just that row.

  • If the input amount is greater than the sumTarget we Exit Sub and scold user.

  • We generate an array of values whose sum, along with user input will be the sumtarget. We then take the sum target and subtract the user input.

    • Afterwards generate a random number between 0 and the new sumtarget.value
    • We then store that rand number and subtract its value from sumtarget.
    • We do this columnsInRange - 1 times.
    • When we step out of the for loop for the last value we set the value to whatever is leftover of sumtarget.
    • With the collection that was created by the steps above we perform a Fisher-Yates Shuffle, so that we don't always the values of collection / our spreadsheet come in a descending order

UPDATE: Thank you for your comments, advice and help. I should have given more thought what form looks like. I have been left with some sort of weird dynamic / static hybrid. A custom UserForm that generates sets of these and then can print into a worksheet would have been cool. Regardless, I took most of Raystafarian's advice. Some of it, like where to store input checking logic, I prefer my way. But generally his advice is spot on. Thank you again.

Sheet 1:

Option Explicit
Private Sub Worksheet_Change(ByVal target As Range)
    SolveSudokuLite.Main target
End Sub

Module SolveSudokuLite:

Option Explicit Sub Main(ByRef target As Range) Dim masterRange As Range Dim rangeToFill As Range Dim valuesToFillRange As GeneratedValuesCollection

        Application.EnableEvents = False
        Set masterRange = Range("B1:E5")
        Set valuesToFillRange = New GeneratedValuesCollection
        If Not Intersect(masterRange, target) Is Nothing Then
            TargetSum.setTargetSum target
            If Not IsValidInput(target) Then Exit Sub
            valuesToFillRange.GenerateValues target
            PrintValues valuesToFillRange, target
        End If
        Application.EnableEvents = True
End Sub

Function IsValidInput(ByVal target As Range) As Boolean
    IsValidInput = True
    If (target.value >= TargetSum.sum) Or Not IsNumeric(target.value) Then
        MsgBox ("WILL NOT CALCULATE FOR ROW " & target.Row & ", USER INPUT INVALID")
        IsValidInput = False
        Application.EnableEvents = True
    End If
End Function

Function PrintValues(ByRef valuesToFillRange As GeneratedValuesCollection, ByVal target As Range)
Dim rangeToFill                     As Range
Dim collectionCounter               As Long
Dim cellInRangeToFill               As Range

    Set rangeToFill = Range("A" & target.Row & ":E" & target.Row)
    collectionCounter = 1
    For Each cellInRangeToFill In rangeToFill
        If cellInRangeToFill.Address = target.Address Then
            cellInRangeToFill.value = target.value
        Else
            cellInRangeToFill.value = valuesToFillRange.Item(collectionCounter)
            collectionCounter = collectionCounter + 1
        End If
    Next cellInRangeToFill
End Function

Class named GeneratedValuesCollection:

Option Explicit
Private GeneratedValuesCollection As Collection

Private Sub Class_Initialize()
    Set GeneratedValuesCollection = New Collection
End Sub

Private Sub Class_Terminate()
    Set GeneratedValuesCollection = Nothing
End Sub

Public Property Get Count() As Long
    Count = GeneratedValuesCollection.Count
End Property

Public Sub Add(num As Long)
    GeneratedValuesCollection.Add num
End Sub

Public Property Get Item(Index As Variant) As Long
     Item = GeneratedValuesCollection.Item(Index)
End Property

Public Sub Clear()
    Set GeneratedValuesCollection = New Collection
End Sub

Public Sub GenerateValues(ByVal target As Range)
Dim userSetValue                    As Long
Dim sumLeft                         As Long
Dim numbersToGenerate               As Long

    userSetValue = target.value
    sumLeft = SetInitialSumLeft(userSetValue)
    numbersToGenerate = NumberValuesToGenerate(target)
    SetValues numbersToGenerate, sumLeft
End Sub

Private Function SetInitialSumLeft(ByVal userSetValue As Long) As Long
    SetInitialSumLeft = TargetSum.sum - userSetValue
End Function

Private Function NumberValuesToGenerate(ByVal target As Range) As Long
Dim rangeToFill                     As Range

    Set rangeToFill = Range("A" & target.Row & ":E" & target.Row)
    NumberValuesToGenerate = rangeToFill.Columns.Count - 1
End Function

Private Sub SetValues(ByVal numbersToGenerate As Long, ByVal sumLeft As Long)
Dim counter                         As Long
Dim value                           As Long

    For counter = 1 To numbersToGenerate - 1
        value = Application.WorksheetFunction.RandBetween(0, sumLeft / 1.25)
        Me.Add value
        sumLeft = sumLeft - value
    Next counter
    Me.Add sumLeft
End Sub

Public Sub ShuffleCollection()
Dim holdValuesArray                As Collection

    Set holdValuesArray = DuplicateCollection()
    Swap holdValuesArray
End Sub

Private Function DuplicateCollection() As Collection
Dim counter                         As Long
Dim maxNum                          As Long

    Set DuplicateCollection = New Collection
    maxNum = Me.Count
    For counter = 1 To maxNum
        DuplicateCollection.Add Me.Item(counter)
    Next counter
End Function

Private Sub Swap(ByRef holdValuesArray As Collection)
Dim randomNum                       As Long
Dim maxNum                          As Long
Dim counter                         As Long

    Me.Clear
    maxNum = holdValuesArray.Count
    For counter = 1 To maxNum
        randomNum = Application.WorksheetFunction.RandBetween(1, holdValuesArray.Count)
        Me.Add (holdValuesArray(randomNum))
        holdValuesArray.Remove (randomNum)
    Next counter
End Sub

Class named TargetSum:

Option Explicit
Private CollectionOfGeneratedValues As Collection

Private Type TTargetSum
        sum As Long
End Type

Private this As TTargetSum

Public Property Get sum() As Long
    sum = this.sum
End Property

Public Sub setTargetSum(ByVal value As Range)
    this.sum = Range("F" & value.Row)
End Sub