Clone variable in Word VBA - vba

Given the range with revisions in it, I need to reconstruct the original text and the modified text. The first solution was to:
Sub OriginalText (ByVal Rng as Range)
Rng.Revisions.RejectAll
OriginalText = Rng.Text
End sub
Yet it turned out that ByVal is not really ByVal. the moment RejectAll is called, all changes are rejected in original document as well, and there is no way to apply them - they're gone from ThisDocument.Revisions.
Is there a (preferably handy) way to copy the variable Rng to any (say,Rng2) in the sub so I can work with the copy of the range without affecting the source?
Is there a way to serialized the range and bring it together, maybe?
Upd: Let me put it this way. Is there a chance to copy the object (Range in my case) so the changes made to the copy won't affect the source? I think that still is the fastest and the most elegant solution.

Range is a marker, even though it returns the content of the range. If you want the text, you need to say so.
Set r = Selection.Range
TextNow = Selection.Range.Text
TextOrig = OriginalText(r)

.RejectAll seems to be clearing all of the revisions (including when the text was first entered). It would be convenient if we could access .Revisions(0), but it doesn't look like this is possible.
Your best bet may be to see if you can store the original document before the changes are made. If that is not possible, you could go through with something like this script and find all of the changes by hand.

Try Range.Duplicate, which seems to create a clone of the range.

Related

How to stop 'Text' changing to 'text' automatically [duplicate]

For unknow reason my Excel VBA editor changes:
Cells(ActiveCell.Row, 1).Value = MyString
into
Cells(ActiveCell.row, 1).Value = MyString
Word "Row" should start with capital "R" but after I type it, it changes to small "r". I have checked the code and I am sure I do not use "raw" as a variable. The macro itself works fine as if it was written "Row". On other workbooks everything is ok (R is capitalized).
Anybody has idea why it happens?
I was also getting a bit tired of from looking where exactly I have declared a Row with a small letter, as far as it was not declared anywhere.
Thus, found a great solution - add the following in a module:
Public Sub TestMe
Dim Row as Long
End Sub
And see the whole code changing. Then you may delete it. Or simply write Dim Row as Long on a new line, somewhere in your code. And then delete it.
VBA isn't case sensitive, so I wouldn't lose too much sleep over it. The editor tries to convert all of the variable cases to however it was dimmed. Most likely the ActiveCell definition was screwed up somehow.
I used variable row in VBA of that worksheet. Then I changed the name of the variable row to something else like MyRowName Although there was no such variable as row in VBA anymore, it still kept lower case for that word. As I mentioned above everything worked fine i.e. ActiveCell.row returned what it should for ActiveCell.Row.
For just aesthetic reasons, I have copied the whole VBA to another worksheet and the bug was crunched. Row returned to Upper case.

Need help refining my excel macro for deleting blank rows or performing another action

Basically what I'm trying to accomplish is to search the document for blank rows and delete them, if any. This works great if there are blank rows to delete; however, if there are no blank rows, the macro ends with an error. I'd be eternally grateful if someone could advise me how to make this into an "if blank rows then this, if none then that"
Sheets ("xml") .Select
Cells.Select
Selection.SpecialCells(x1CellTypeBlanks).Select
Selection.EntireRow.Delete
Enter my second macro (this part works fine)
Regards
Let me point you to the canonical:
How to avoid using Select/Activate in Excel VBA macros
So you can start to understand why your current code fails or performs undesired operation. What happens when there are no blank cells in your selection? You'll get an error. Why?
Because in that circumstance, Selection.SpecialCells(xlCellTypeBlanks) evaluates to Nothing. (You can verify this using some debug statements) And because Nothing does not have any properties or methods, you'll get an error, because you're really saying:
Nothing.Select
Which is a null program, does not grok, does not compute, etc.
So, you need to test for nothingness with something like this:
Sheets("xml").Select
Cells.Select
If Not Selection.SpecialCells(x1CellTypeBlanks) Is Nothing Then
Selection.SpecialCells(x1CellTypeBlanks).EntireRow.Delete
End If
I still suggest avoiding Select at all costs (it is superfluous about 99% of the time and makes for sloppy code which is difficult to debug and maintain).
So you could do something more complete following that line of thought:
Dim blankCells as Range '## Use a range variable.
'## Assign to your variable:
Set blankCells = Sheets("xml").Cells.SpecialCells(xlCellTypeBlanks)
'## check for nothingness, delete if needed:
If Not blankCells Is Nothing then blankCells.EntireRow.Delete
Follow-up from comments
So in VBA we are able to declare variables which represent objects or data/values, much like a maths variable in an equation.
A Range is a type of object part of the Excel object model, which consists of the Workbook/Worksheets/Cells/Ranges/etc. (far more than I could hope to convey to you, here)
http://msdn.microsoft.com/en-us/library/office/ff846392(v=office.14).aspx
A good example of why to use variables might be here if you scroll down to the "Why Use Variables" section.
http://www.ozgrid.com/VBA/variables.htm
This is of course very simple... but the reader's digest version is that variables allow us to repeatedly refer to the same object (or value for sipmle data types) without explicitly referring to it each time.
THen there is the handy side-effect that the code bcomes more easy to read, maintain and debug, when we use variables instead of absolute references:
Dim rng as Range
Set rng = Sheets(1).Range("A1:Q543").Resize(Application.WorksheetFunction.CountA(Sheets(1).Range("A:A"),))
Imagine that fairly (but not ridiculously) complicated range construct. If you needed to refer to that range more than once in your code, it would be silly not to assign it to a variable, if for no other reason than to save your own sanity from typing (and possibly mistyping a part of it). It is also easy to maintain, since you need only modify the one assignment statement and all subsequent references to rng would reflect that change.

That bookmark *does* exist

I'm trying to get to a specific point in my Word document from excel VBA. My ultimate goal is to copy new text into the bottom of the document.
So I put a bookmark at the bottom of my document and did this:
wDoc.Goto What:=wdGoToBookmark, Which:=wdGoToFirst, Name:="TemplatePage"
And it tells me "That bookmark does not exist." Which is funny, because it clearly does. It says the same thing about all my bookmarks.
This does not give me an error, instead it doesn't do anything:
wDoc.Goto What:=wdGoToBookmark, which:=wdgotofirst
Am I missing something obvious here?
Am I missing something obvious here?
IMO you are missing something, but the use of the word "Goto" can create the wrong impression.
.Goto actually makes the object that you specify "goto" the specified location. So if that object is a range, you shouldn't expect the Selection to change.
The other thing is that you have to omit the "which" parameter in this case. I don't know how you could determine that from the documentation - it's a question of trial and error with some of these range-related things.
Since the range implied by wDoc or wDoc.Content won't change anyway, you will lose any useful information immediately by executing the .Goto as a command. If you use it as a function, you get a range, so you can use, e.g.
Dim r As Word.Range
Set r = wDoc.Goto(What:=wdGoToBookmark, Which:=wdGoToFirst)
Debug.print r.start, r.end
Or if you want to select the resulting range, you can use
r.Select
But if you want to set a range
Dim r As Word.Range
Set r = wDoc.Bookmarks("TemplatePage)
is (IMO) simpler and you already have the code that actually selects it if that is what you need.
Ok, so apparently the GoTo simply doesn't work. I tried all sorts of variations on Page, Section, Bookmark, they either did nothing, gave errors, or even moved to the wrong place (selected text in the header!)
Instead, you can try:
wDoc.Bookmarks("TemplatePage").Select
Works like a champ.

Shapes not tied to worksheets?

ok so,
I was hoping to have some stuff in my excel app persist, as in hang around, and I want something a little more reliable than global variables as these are reset when code is edited or if say the app is halted. (which does often happen)
So I've been using shapes, and they work good, but they rely on at least one worksheet always being constant right? as shapes are tied to sheets, if the sheet with the shapes gets deleted the shapes go away. And the users often delete / add new sheets, theres no one sheet that is always a constant, and they wouldnt let me force that on them either.
so is there a way to make shapes tied to workbooks instead of sheets? so then if a sheet is deleted with shapes on it, the shapes wont disapear.
any help or other suggestions appeciated
Edit
Thanks again to: #David Zemens for the answer that got me through, just in case anyone ever looks at this down the road, the code to add a named range is: workbook.Names.Add Name:="Name", RefersTo:="value" - you NEED to add refersto or it will error. you can put in a temp value like "temp" and set the value later, but you have to have refers to when you add
You can use Named Ranges to save string data between sessions. They can be children of the Workbook or of specific Worksheets. You will want the former. From the Formula Ribbon, Names Manager, Define Name like so:
Then, in your VBA, you can retrieve and set this range's value like:
Public Const CSVFileName as String = "sFileName"
Sub YourSubroutine()
Dim nm As Name
Set nm = ActiveWorkbook.Names(CSVFileName)
'Get the value:
MsgBox Replace(Replace(nm.Value, "=", vbNullString), """", vbNullString)
'Set the value:
nm.Value = "C:\documents\filename.CSV"
End Sub
The value associated with the Named Range persists beyond runtime, it is basically a property of the workbook.
I will post this now for you to review. I will try to work up an example of the XML Customer Data, and will revise my answer with that, later.

How to avoid default property gotchas in VBA?

I only use VBA occasionally, and every time I come back to it I get caught out by some variation of the following:
I have a Range object, currentCell, that I use to keep track of what cell I'm working with in the spreadsheet. When I update this to point to a different cell, I write:
currentCell = currentCell.Offset(ColumnOffset:=1)
The problem is that I've forgotten the Set keyword, so what the above line actually does is use the default property of the Range objects:
currentCell.Value = currentCell.Offset(ColumnOffset:=1).Value
So the contents of the current cell are overwritten by what's in the new cell, and my currentCell variable hasn't changed to point to a new cell, and I get filled with rage as I realize I've made the same mistake for the hundredth time.
There probably isn't a better answer than to put a post-it on my monitor saying "Have you remembered to use Set today?", but if anyone has any suggestions to help me, I'd appreciate hearing them. In particular:
Is there any way to turn on warnings when you implicitly use default properties? I have never used them like this on purpose, I'd always call Range.Value if that's what I meant.
Is there any good practice for marking variables as "this should only be used to read from the spreadsheet"? In most code I write, almost all my variables are for gathering data, and it would be handy to get a warning if something starts inadvertently editing cells like this.
It took me a while to understand how you are running into problem since I do not think I have ever had this problem in spite of using range objects for years. After thinking about things, I realized I do the following 100% of the time when working with cells - I use the offset or cells functions constantly - I rarely use Set to redefine a current variable.
If I have a loop I am iterating through to go through the spreadsheet, I may do something like
Dim startRng as Range
Set startRng = range("A1")
for i = 1 to 100
startRng.offset(i,0).value = i
startRng.offset(i,1).value = startRng.offset(i,0).value
next i
or
for i = 1 to 100
cells(i,0).value = i
cells(i,1).value = cells(i,0).value
next i
Either of these notations means I almost rarely have to use Set with a range object - almost always this happens once (if at all) and indicates the first cell in a range I will iterate over or reference.
It is also really clear what the offset is - since you specify a row/column - which makes it really straightforward what is happening in the code and easier to track since it references a single cell. You don't have to track down and trace backwards to the last 3 places you update a currentCell Range object.
Adopting a style of coding using these sorts of styles should eliminate nearly all these errors you are making. I am quite serious when I say I cannot remember ever having made a similar error in all my years coding VBA - I use the offset and cells functions continuously in my code (loops in these examples, but I use similar methods with all other examples in code) rather than setting ranges to new ranges. The side effect is that when you are setting a range in your code, it is almost ALWAYS immediately following a Dim statement and much more clear.
Whatever you choose to do, you'll need the post-it note, I'm afraid. After all, setting a range object's value to the value in another cell is a perfectly valid and common thing to do. The code has no way of knowing that you want it to do anything other than what you ask it to.
You could try checking your range object's address before and after you update it to make sure it's different, but if you remember to do that, you would be better off simply using the set keyword to update the object the way you intended.
Now that this issue has enraged you to the point of posting your question, I imagine that you'll never forget it again, regardless of how much time goes by before your next visit to VBA. So maybe you won't need the post-it note after all.
Is there any way to turn on warnings when you implicitly use default properties?
No.
Is there any good practice for marking variables as "this should only be used to read from the spreadsheet"?
Well, you could make your own variable naming convention, à la Making Wrong Code Look Wrong, but you'll still have to check your own code visually and the compiler won't help you do that. So I wouldn't rely on this too much.
A better option is to circumvent the need for repeatedly redifining currentCell using .Offset altogether.
Instead, read the entire range of interest to a Variant array, do your work on that array, and then slap it back onto the sheet when you're done modifying it.
Dim i As Long
Dim j As Long
Dim v As Variant
Dim r As Range
Set r = Range("A1:D5") 'or whatever
v = r.Value 'pull from sheet
For i = 1 To UBound(v, 1)
For j = 1 To UBound(v, 2)
'code to modify or utilise element v(i,j) goes here
Next j
Next i
r.Value = v 'slap v back onto sheet (if you modified it)
Voilà. No use of default properties or anything that could be confused as such. As a bonus, this will speed up your code execution.
There probably isn't a better answer than to put a post-it on my monitor saying "Have you remembered to use Set today?", but if anyone has any suggestions to help me, I'd appreciate hearing them. In particular:
I would slightly change the wording on the Post It
"Are you sure you have not forgotten using Option Explicit and Error Handling?"
Otherwise trust me there is no better way! Having said that, I would like to confirm that "Using Set" is the least of your worries. What should be on the top of your main worries is "Writing Good Code" and this doesn't come overnight. It all comes by practice.
My advice to all beginners. Never assume! For example, .Value is the default property of the range so
Range("A1") = "Blah"
is correct. But still avoid using that.
Always Fully qualify your variables
Always Use Option Explicit
Always Use Error handling
For example, This works.
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim rng As Range
On Error GoTo Whoa
Set ws = ThisWorkbook.Sheets("Sheet1")
Set rng = ws.Range("A1")
rng.Value = "Blah"
Exit Sub
Whoa:
MsgBox Err.Description
End Sub
Now let's try the above code without using the Set command. Try the below code. What happens?
Option Explicit
Sub Sample()
Dim ws As Worksheet
Dim rng As Range
On Error GoTo Whoa
ws = ThisWorkbook.Sheets("Sheet1")
rng = ws.Range("A1")
rng.Value = "Blah"
Exit Sub
Whoa:
MsgBox Err.Description
End Sub
Recommended Read.
Topic: To ‘Err’ is Human
Link: http://siddharthrout.wordpress.com/2011/08/01/to-err-is-human/
I think enderland has highlighted the basic solution, which is to handle processing multiple cells in a loop. To take it further, I'd suggest using a For Next loop for cycling through cells. Probably on of the most common bits of code I write is something like:
Dim cell as Excel.Range
Dim rngCellsToProcess as Excel.Range
Set rngCellsToProcess = 'whatever you set it to
For each cell in rngCellsToProcess
'do something
Next cell
This won't eliminate the need for Set, but may help remind you to use it, while making it clearer what's going on.
Maybe write your own custom function and use it instead ?
Sub offset_rng(ByRef my_rng As Range, _
Optional row As Integer, Optional col As Integer)
Set my_rng = my_rng.Offset(row, col)
End Sub
Can be used like this:
Sub test()
Dim rng As Range
Set rng = Range("A1")
offset_rng my_rng:=rng, col:=1
rng.Value = "test1"
offset_rng my_rng:=rng, col:=1
rng.Value = "test2"
offset_rng my_rng:=rng, col:=1
rng.Value = "test3"
offset_rng my_rng:=rng, col:=1
rng.Value = "test4"
End Sub