简体   繁体   中英

Follow-Up to VBA inheritance via construction, constructor not working?

This is a follow-up to this question.

Here's my use case: I want to compare two Excel files cell-by-cell and highlight the cells that are different. Each file will have a few sheets and each sheet will have a few columns, each with a header and a few values (as typical). Here is my draft activity diagram for the comparison code:

Activity Diagram

And here is my draft class diagram:

Class Diagram

My goal is to make VBA less cumbersome for the types of things I do often (such as comparing new and old versions of spreadsheets). That is, I want it to work more like Python... In particular, I want to write code like this:

For Each Sheet1 In File1
    Name1 = Sheet1.Name
    If File2.sheet_dict.Exists(Name1) Then
        Sheet2 = File1.Sheets(File2.sheet_dict(Name1))
        Sheet2.Checked = True
        For Each Col1 In Sheet1.cols
            hdr = Col1.Header
            If Sheet2.header_dict.Exists(hdr) Then
                Col2 = Sheet2.cols(Sheet2.header_dict(hdr))
                Col2.Checked = True
                For Each Val1 In Col1.Vals
                    Val2 = Col2.Vals(Val1.row_number)
                    Val2.Checked = True
                    If Not Val1.Match(Val2) Then
                        Val1.formatBad()
                        Val2.formatBad()
                    End If
                Next Val1
                For Each Val2 In Col2.Vals
                    If Not Val2.Checked Then
                        Val2.formatBad
                    End If
                Next Val2
            Else
                Col1.formatBad()
            End If
        Next Col1
        For Each Col2 In File2.cols
            If Not Col2.Checked Then
                Col2.formatBad
            End If
        Next Col2
    Else
        Sheet1.formatBad()
    End If
Next Sheet1

For Each Sheet2 In File2
    If Not Sheet2.Checked Then
        Sheet2.formatBad()
    End If
Next Sheet2

Of course I'd have load all the data into the objects first, but you get the idea. Is it crazy to try to do something like this in VBA?

It's not crazy to want to use the object oriented language features of VBA but the use case you're giving isn't that far removed from the built-in objects that excel already provides and so it isn't clear how much you'll gain from the complexity you'll be adding to get it. There's a fair bit of power to be harnessed in excel vba but it's best to play off of it's strengths whenever you can.

You could just as easily colorize differing cells more efficiently by using the code at the end of this post - obviously not to do all that you intend but clearly something that doesn't require resorting to OO for single columns and rows.

Excel and vba are quite different than what programmers are used to coming from something like python that provide full class inheritance. With VBA you're stuck with only having interface inheritance which will allow you to reuse code. But if you're not careful you could very easily end up with a lot of stub code that you have to copy from class to class just to be able to satisfy the interface you want your classes to implement.

There's also another thing you definitely have to wrap your head around coming from a conventional OO language and that's how much data you replicate in your in-memory objects as opposed to just leaving on a worksheet and accessing them as required. There's a natural tendancy to want to load everything into an object and manipulate from there - but it's an urge you should really think twice about in this environment.

If you have an existing server back-end that will validate the data that moves between your worksheets and a database then at least you have a way of segregating normal MVC concerns. In effect you'd be using excel as a sort of web page with the additional functionality that spreadsheet users love. If you don't have a back-end then you really have to be very careful about validating your models and data in this environment. You should get used to the idea of protecting the worksheet except for those cells that users will have to input data into (assuming you are writing code to benefit others than just yourself). In fact it's a good idea to color the input cells and the calculated cells with distinct colors to highlight this difference. The latter should be protected whereas the former where needed can trigger events that will validate input and update the model state (and ideally work with a back-end if you've got one).

Protecting cells also allows you to hide state information in a well-defined sections of the worksheet that can be used to reference back to working objects. In fact good use cases are those that segregate well defined cell blocks as an user interface to specific class instances.

Where possible you should use ranges to reference sections on the same worksheet and on others. Named ranges are your friend here. Data validated lists are also very helpful for contiguous data and should be used whenever possible as they are very efficient at what they do. For non-contiguous data sets that are limited in size, you can use ActiveX combo-boxes which can reference in-memory object instances if their event handlers are passed an id unique to the latter.

When checking for event changes, you should be careful of the Worksheet_Change polling that you'll see a lot of examples of on the web. This can chew up a fair bit of time if you aren't careful.

To summarize: use the whatever power you can harness from excel and refrain from re-inventing wheels.

' Compares the sheet 1 of the workbook you're in
' with sheet1 of the workbook file in 'Filename'
' and colors the cells that differ between the two.
Sub compare_workbooks_sheet1()
    Dim Filename As String
    Filename = "C:\MyBook.xlsm"

    Dim wrkbk1 As Workbook
    Set wrkbk1 = Workbooks.Open(Filename:=Filename)

    Dim sht1 As Worksheet ' worksheet you're in
    Dim sht2 As Worksheet ' worksheet you've opened to compare
    Set sht1 = wrkbk1.Worksheets("Sheet1")
    Set sht2 = ThisWorkbook.Worksheets("Sheet1")

    Dim row As Long, col As Long
    With sht2
      For row = 1 To sht1.UsedRange.Rows.Count
          For col = 1 To sht1.UsedRange.Columns.Count
              If sht1.Cells(row, col) <> sht2.Cells(row, col) Then
                  .Cells(row, col).Interior.ColorIndex = 5
              End If
          Next
      Next
    End With

    wrkbk1.Close

End Sub

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM