Sure thing! Hope it helps, and look forward to seeing the next bits
Sure thing! Hope it helps, and look forward to seeing the next bits
Me, too. I'm anxious to dig into this but first I need to complete and deliver a project by BoB on Monday. So, back it's to on the clock.
Ron
Windermere, FL
Jason,
How do we avoid using Activate to change which file I'm reading from, writing to, or closing? By having the function or subroutine open the file immediately before I read or write using it?
When do I cut/delete the information that I've moved into rngActivity.select (you called it rngLesson)?
Here is your code updated for the notes in your two followup messages. Do I have this right? (as above, I changed references from Lesson to Activity)
[VBA] 'selecting always helps when working with ranges
Sub SelectMyActivity()
Dim rngSearch As Range
Dim rngActivity As Range
Set rngSearch = ActiveDocument.Content
Set rngActivity = FindMyActivity(rngSearch)
'find it and select it
rngActivity.Select
End Sub
'returns an expanded range, selecting the entire document if page break not found
Function FindMyActivity(rngLookWhere As Range) As Range
Dim lStart As Long
lStart = rngLookWhere.start
With rngLookWhere.Find
.Text = "^m"
'if we found our page break,reset the beginning of our found range to the original start
If .Execute = True Then
rngLookWhere.start = lStart
LastActivity = False
Else
Set rngLookWhere = ActiveDocument.Content
LastActivity = True
End If
End With
Set FindMyActivity = rngLookWhere
End Function
[/VBA]
Thanks,
Ron
Windermere, FL
Instead of using ActiveDocument, start setting variables to the particular documents.
Dim oDocSource as Document
dim oDocNew as Document
Set oDocSource = ActiveDocument when you start...
Then as you search the range of that oDocSource (oDocSource.Content) to get your activities, you create new documents (Set oDocNew = Documents.Add())
As you save and close the oDocNew, you can set it to another new document.
You only need to .Select these ranges as you're stepping through the code in order to understand whether you're finding the right range. And you only need to activate the particular document variables to make sure you're getting info from your source document, and putting info into your recently created document.
At the end of the day, there is no reason to to select ranges or activate documents... computers don't need to turn the lights on to do their work. Only humans do.
Make sense?
Sorry, a little quick with my response.
1) What is the LastActivity variable, and why do you need it? If you need it, then you should probably do something with it (as well as declare it-- you are using "Option Explicit" at the top of all of your modules, right?)
2) At some point you will translate this code into something like...
(warning: this is pseudo code)
[vba]
Set oDocSource = ActiveDocument
'loop through all of the activities in your source document
Do
'get the activity
set rngActivity = FindMyActivity(oDocSource.Content)
'do a bunch of stuff with your activity (splitting into separate testable routines)
rngActivity.Copy
'create your new activity document
set oDocNew = Documents.Add
oDocNew.Paste
oDocNew.Save '(with whatever naming parameters you've done above)
oDocNew.Close
'loop until my search returned the last activity in my source document
Loop Until rngActivity = oDocSource.Content
[/vba]
Jason,
Thanks, setting Document variables to makes sense.
What still puzzles me is at what point to I .cut the range I've found from the source document so when I return to it, it's 'top' is now just below the most recently found "My Notes:" instance?
When rngLookWhere = ActiveDocument.content (because there is no trailing ^m) how does the program know that I'm done with that Source file and it's time to iterate the Unit number? I decided to add the flag LastActivity and flip it from False to True. I have in mind using it in the calling routine to control a loop.
Whoops! I see: Loop Until rngActivity = oDocSource.Content
Thanks,
Ron
Windermere, FL
There are many ways to do this, so don't take my suggestion as the "right" way... I just happen to be the one responding.
You can .Cut the material to continue shortening your source file... and then ultimately you can close the source file without saving changes. I'm not a fan of this approach, because I don't like modifying "source data" in general.
But you can also modify your range each time. Remember that ranges are primarily defined by their .Start and .End property (at the simplest level). So the first "Activity" in your source document will be something like rngActivity.Start = 0 and rngActivity.End = 1254.
So as you move through your source document... keep changing the .Start value of your RngSearch to the End of your rngActivity value.
You may need to change your logic so that then your FindMyActivity function doesn't return the rngSearch.Parent.Content when it doesn't find a page break (preferable to ActiveDocument.Content in this context), but rather returns a range which starts with rngSearch.Start and ends with rngSearch.Parent.Content.End. This approach would allow you not to modify your source document at all.
While you're playing with this... it's going to be helpful to keep going through the little chunks. Figure out how to iterate through your source document the right number of times, identifying the correct ranges each time (just .Select it and show a message box as your proof of concept).
Once you've got the (pseudo code) For Each MyLesson in oMyDoc.Activities part worked out... then it's time to sort out how to play with a specific range (activity). And all you have to do is write that code to deal with a passed in range. As you play with that code, you just manually select the activity (since you already know how to do that), and start extracting the bits from there by using the immediate window:
GetInfoFromMyActivityRange Selection.Range, "Lesson Name"
And that looks something like (there are so many variations to this, this is just the first one I thought of:
[VBA]
Public Function GetInfoFromMyActivityRange (rngActivity as Range, sInfoWhat as String) as String
Dim sRet as string
Select Case sInfoWhat
Case "Lesson Name"
sRet = rngActivity.Paragraphs(1).Range.Text
Case Else
sRet = "I dunno!"
End Select
GetInfoFromMyActivityRange = sRet
End Function
[/VBA]
That's what I mean by breakable chunks. You're mixing and matching your tasks when you ask me a question about iterating the unit number in the context of being "done" with the source file.
First... find your first activity.
Then... figure out how to just loop through each activity in your document and stop at the end (which is not just a learning experience, but a decision: how do you want to handle your source document?)
Then... figure out how to deal with your individual activities (and what information is contained in them).
It becomes much easier when you are correctly identifying your own tasks. Experience will help in that identification, so that is part of the lesson.
It really does help not to necessarily think linearly when designing these kinds of approaches... just because you're going to find your first lesson, get info about your first lesson, create a new document based on your first lesson, put some stuff in that new document based on your first lesson, go to your second lesson... doesn't mean you should code it that way.
Invariably, if you code in a linear fashion... you find out you're screwed when you get to your last lesson... because there are no more page breaks. Grin.
Much better to figure out how to find all of your lessons in any way you choose... and then move on to getting whatever info you want about a particular lesson. Etc etc.
Jason,
I'm looking at my "problem" of needing to get my 2nd activity into the file with the first one, all without resorting to GOTO branches.
One solution is to close the output file after adding an Activity to it. Then, as I process my "header info" in the next activity, I open the new filename to see if it already exists, if it is the 2nd or subsequent Activity for a Lesson it will, else the error will tell me to create a new document/file. How easy is it to trap and use the file open error (no file by that name) in IF/THEN/ELSE logic?
That Loop Unit rngActivity = oDocSource.Content looks like it might be a problem. If I delete the Activity from the oDocSource as I paste it into oDocNew when I (or my program) does this test oDocSource will be empty and <> rngActivity. Am I wrong?
Thanks,
Ron
Windermere, FL
Jason,
Thanks for the extended advise. I'll stop and work on the bits, one at a time to learn more about what I have. If I go with the technique of deleting the activities as I paste them, the deletion will be from a copy of the source file, not the file itself. But, I like the idea of moving the start and end pointers, that will work much better and cutdown on file reads and writes which are time-consuming, relatively speaking.
First, I believe that I'll sleep on this overnight.
Thanks,
Ron
Ron
Windermere, FL
Couple quick responses:
1) don't worry too much about optimizing your code to get better "speed" in VBA. Once you stop using the Selection object (i.e., you program correctly), the benefits of code optimization are marginal, for the most part. There are exceptions to this (like everything), but they are pretty small, at least from what I'm looking at in your task list here. If you were processing tens of thousands of excel rows and writing them to table cells, it might be different.
2) Sleep is good
3) I sound full of myself at times... take it with a grain of salt. I'm trying to help you without doing it all for you, but I'm actually not as pompous as I sound. Grin.
It sounds like you're getting a little overwhelmed/mentally stuck by the whole looping through multiple activities and what to do with your source document, etc.
Tell you what. Work out your process for just dealing with a selected Activity and getting whatever info you need from the selected lesson into a totally new document, and saving that document as the "right" name.
Just make sure your process works for whatever activity in the source document you've selected. Don't worry about whether you copy or cut the info, just get the info you need and put it in a new document.
Checking to see if the file already exists and looping through all the activities is easy... and I'll be able to give you a really good code sample if I see the nitty-gritty (code which works for any type of activity).
This is an interesting little problem, and I'm hoping to help. At the same time, if I find some time next week, I can probably just give you the whole thing... but it depends on whether you're interested in just getting it done or learning all the available lessons in this relatively complex process.
There are trade-offs, I'd rather learn as many lessons as I can but on the other hand at some point I have to have processed the 56 files and shipped the "lesson" files to another department in the company. There are some other priorities on the list for early this week, so, I'll have time to work on learning.
Thanks, again.
Ron
Windermere, FL
Jason,
After reopening a file and doing .MoveEnd, how do I get VBE to allow me to insert my next Activity after the one in the file? Here's a code fragement:
[vba]Sub A_Fragment()
If myOutFile <> LastFileName Then
Set oDocNew = Documents.Add
Else
Call Open_File(myOutFile, ".rtf", SrcDir)
Set oDocNew = ActiveDocument
oDocNew.Content.MoveEnd
End If
oDocNew.Paste ' <<< how do I InsertAfter instead of Paste over?
End Sub
[/vba]
Thanks,
Ron
Windermere, FL
.Content is just a description of the range of the entire MainStory of the document. You can't .MoveEnd on that.
Also... just to clarify the concept... the .End of the range is the, well.. the end... so you would want to .MoveStart if you want to insert after (even though that wouldn't work either on the .Content object either).
What you can do is (pseudo code):
Set myRange = oDocNew.Content
myrange.Collapse wdcollapseend
myrange.paste (does paste work off the range object?)
Jason,
Yes, Paste is a method of the Range object.
I was about to ask you about how to display the FileOpen dialog but I noticed your Dialogs answer to another user and that sent me to Dialogs in Help, so, I have that answer, now.
Can I use that (FileOpen) dialog to pick and then set a starting directory location?
The matter of pasting a second activity into the RTF file that already has one activity is still a challenge. As in, it's still not working. I'm letting that sit for a moment while I test and debug some other stuff.
Cheers!
Ron
Windermere, FL
Jason,
Is there a way to get the wdDialogFileOpen to tell me what directory is chosen, whether or not I open a file at the same invocation of the dialog?
[vba]Sub ShowOpenDialog()
Dim dlgAnswer As String
dlgAnswer = Dialogs(wdDialogFileOpen).Show
' Return value Description
' -2 The Close button.
' -1 The OK button.
' 0 (zero) The Cancel button.
' > 0 (zero) A command button: 1 is the first button, 2 is the second button, and so on.
End Sub
[/vba]
The return values tell me a little bit but leave me wondering what file I opened and what directory it was in. Since that file is now the ActiveDocument, I can use the Name and Path properties IF/WHEN I open a file. But, if all I do is use the dialog to change directories, I'm a bit adrift.
Thanks,
Ron
Windermere, FL
The dialogs collection is pretty under-documented.
Check this out:
http://word.mvps.org/faqs/macrosvba/BrowsDialog.htm
Thanks, Jason.
I pulled the several downloads mentioned in the article and made a PDF of the article, itself.
Cheers,
Ron
Windermere, FL
Jason, et al,
Apparently, the only remaining issue is that of getting the 2nd Activity's FormattedText inserted into oDocNew after the existing text. Here is the relevant block of code from my program. My challenge is in the Else block, when I run the code, the only thing added to oDocNew is a Paragraph mark (symbol) which I interprete as meaning that I succeeded in inserting a CrLf.
What should the target document (oDocNew) look like after the line rngDocNew.Collapse Direction:=wdCollapseEnd is executed? When I change to that window, how should my document appear? For instance, when I select an Activity, it is highlighted in the source doc window.
[vba] Do Until ActivityEnd = oDocSource.Content.End
Call Find_MyActivity2(oDocSource.Content, rngActivity, ActivityStart, ActivityEnd)
'find it and select it
myOutFile = Get_MyOutFile(rngActivity)
rngActivity.Select
rngActivity.Copy
oDocScrap.Range.Paste
Set rngScrap = oDocScrap.Content
Set rngScrap = Delete_1stLine(rngScrap)
rngScrap.Select
rngScrap.Cut
' If myOutFile = LastFileName Then
' myOutFile = myOutFile & "a"
' End If
If myOutFile <> LastFileName Then
Set oDocNew = Documents.Add
oDocNew.Range.Paste
Else
' Debug.Print myOutFile
' Stop
Call Open_File(myOutFile, "MyOutFile", "", SrcDir)
Set oDocNew = ActiveDocument
Dim rngDocNew As Range
Set rngDocNew = oDocNew.Content
rngDocNew.Collapse Direction:=wdCollapseEnd
rngDocNew.FormattedText = rngScrap.FormattedText
End If
File_Saved = Save_oDocNew(oDocNew, "MyOutFile", myOutFile, SrcDir)
If File_Saved <> True Then
Stop
Else
LastFileName = myOutFile
End If
oDocNew.Close
Loop
[/vba]
Thanks in advance for your assistance.
Cheers,
Ron
Windermere, FL
I might need to see more than the do loop to help you clean it up a bit. But it's looking like you're really separating things out well. Couple of comments before I answer your question (of course, you can just skip to the end too, grin).
1. Open_File is a subroutine? Why not make it a function which returns the document it opens? Then you can...
set oDocNew = Open_File(myOutFile, "MyOutFile", "", SrcDir)
2. It looks to me like you would benefit from using the Optional Parameter (no reason to pass in "" as an argument into a proc you've developed)
3. FindMyActivity2... I'm a little confused by your parameters there... why are you passing a range, and what looks to be the start and the end of that range? I assume it's because you're only *really* passing in the activedocument.content range... and then returning the rngActivity as a return value... but you don't need to return start and end-- they come along as properties of your rngActivity.
4. This is somewhat of a preference thing-- but putting Dims in the middle of your routines can make it harder to troubleshoot later. The good news about this comment is that once we're getting into stylistic type stuff, you know you're getting close on the actual coding.
5. Now, to your main question: the reason you're getting the wrong result is because that's exactly what you've told it to do. Remember, ranges are live addresses to something which is already in the document. So when you do the following:
rngScrap.select
rngScrap.Cut
... what is left of rngScrap?
The same thing that is left when you select 3 paragraphs and cut them... an insertion point. So when you later do this:
rngDocNew.FormattedText = rngScrap.Formatted text
You've forgotten all about the stuff you had in your clipboard.
rngDocNew.Paste would work.
However, I do take exception to your naming convention here... you don't want to call it rngDocNew... you want to call it rngInsertHere (or something similar).
I make that point because, even without being able to see the rest of your code, your naming convention was pretty much good enough for me to get an idea of what was going on-- which is fantastic.
One other point: You do not need to .select a range to work with it... but it is helpful when you're stepping through your code to type rngScrap.Select in the immediate window at any given point to see what you're actually working with. However, you can probably get rid of every line of code in which you use .Select (and you should see if you can get rid of every line of code which uses ActiveDocument, and instead try to pass the document object around).
Looking good, Ron. I would love to see your code project at the end... I might be able to do a quick sweep through and give you some last coding style pointers (without slowing down the actual need to get the project out the door).
- Jason