Computer envelopes cartoon

Hasznos kiegészítő Word körlevélhez

A Word körlevélkészítés funkciójával (az angol nyelvű verzióban Mail Merge) egyszerűen tudunk tömegesen megszemélyesített leveleket létrehozni, vagyis olyan leveleket, amelyeknek bizonyos részei azonosak, bizonyos részei (például cím, megszólítás, számmezők, de akár komplett szöveges bekezdések) pedig levelenként változnak. A Word által így létrehozott körlevél egy, akár több száz vagy több ezer oldalas dokumentum. Ez az output tökéletesen megfelel, ha nyomtatni akarjuk a leveleket; de mi van, ha nekünk levelenként különálló Word vagy Pdf fájlokra van szükségünk? A Word beépített funkciója erre nem kínál megoldást, de egy rutinos makrókészítő nem esik kétségbe, hanem egy kis szkript segítségével áthidalja a hiányosságot.

Szöveges formában megfogalmazva az alábbi műveletsort szeretnénk a makrónkkal elvégeztetni:

  • keresse meg az első levelet,
  • másolja ki egy új Word fájlba, a formázás megtartása mellett,
  • adjon nevet a fájlnak: feltételezzük, hogy minden levél a címzéssel kezdődik, ahol az első szöveges sorban szerepel a címzett neve (és tegyük fel hogy ez a név egyedi)
  • mentse el a Word fájlt ezen a néven, ugyanabba a könyvtárba, ahol a körlevél dokumentum is szerepel,
  • ugyanezen a néven mentse el a levelet Pdf formátumban is,
  • a fenti műveletsort végezze el az összes többi levéllel.

Az egyes levelek azonosítása

Word Section Break A körlevél dokumentumunk feldarabolásához azt fogjuk felhasználni, hogy az egyes levelek egy-egy szakaszt (Section) alkotnak és közöttük szakasztörés (Section Break) található.

A rutinunk kerete így néz ki:

Option Explicit
Dim FajlNev  As String
Dim Korlevel As Document
Dim TempDoc  As Document

Sub Korlevelbol_Egyedi_Levelek()
    
Dim i As Long

Set Korlevel = ThisDocument

    Application.Browser.Target = wdBrowseSection

    For i = 1 To ((Korlevel.Sections.Count) - 1)

    '   [Műveletek]

        Application.Browser.Next
    Next i

End Sub

Az Application.Browser tulajdonsággal az ún. Selct Browse Object eszközt címezhetjük meg. Ez a kis eszköz amúgy megtalálható a Word jobb oldali gördülősáv alján.

Word Select Browse Object

Az  Application.Browser.Target tulajdonsággal beállíthatjuk, hogy az eszköz léptetésekor hova ugorjon a kurzor. Az alapbeállítás a következő oldalra ugrás, de lehetőségünk van többek között a megjegyzések, lábjegyzetek, címsorok, grafikák, táblák – és az Application.Browser.Target = wdBrowseSection utasítással a szakaszok – között lépdelni.

Magát a léptetést az Application.Browser.Next utasítással végezzük el. Felmerülhet a kérdés, hogy emellett az utasítás mellett mi szükség van a For … Next ciklusra. Az Application.Browser.Next utasítás csak a kurzort lépteti a következő szakaszra, nekünk viszont arra is szükségünk van, hogy a programunk újra és újra, minden egyes szakaszon végrehajtsa a cikluson belül szereplő utasítássort.

Feltűnhet, hogy a For … Next ciklusban a szakaszok számánál egyel kevesebbszer hajtjuk végre a műveletsort. Ennek oka, hogy a Word által generált körlevél dokumentum mindig szakasztöréssel végződik, és számláláskor az utolsó szakasztörés utáni részt is egy szakasznak veszi (tehát ha csak egy darab levelünk lenne, azt is két szakasznak számolja), viszont a legutolsó szakaszra vonatkozóan a cikluson belül végrehajtandó műveleteket (például másolás) már nem tudná elvégezni, és hibaüzenetet kapnánk – de ezen műveletek elvégzésére nincs is szükségünk.

Másolás külön fájlokba

A cikluson belüli első utasítással ki kell jelölnünk egy egész szakaszt, egész pontosan a kurzor és a kurzor utáni szakasztörés közötti szöveget. Ezt a következő utasítássorral tudjuk elérni:

        
        Korlevel.Bookmarks("\Section").Range.Copy

Bookmarks(“\Section”) objektum egyike az előre definiált könyvjelző objektumoknak, az aktuális szakaszt definiálja (vagyis azt a szakaszt, ahol a kurzor éppen áll), az elejétől a végéig, beleértve a lezáró szakasztörést is. Ahhoz, hogy egy másolható szöveget kapjunk, nem egy Bookmark típusú objektumra van szükségünk, hanem egy Range típusúra, ezért használjuk a könyvjelző .Range tulajdonságát. A másolást értelemszerűen a .Copy metódussal tudjuk elvégezni.

Következő lépésként hozzunk létre egy új, üres dokumentumot és rendeljünk hozzá egy változót:

        Set TempDoc = Documents.Add

Majd ebbe az üres dokumentumba másoljuk be az előző lépésben kimásolt szakaszt:

        With TempDoc.PageSetup
            .TopMargin = Korlevel.PageSetup.TopMargin 
            .BottomMargin = Korlevel.PageSetup.BottomMargin 
            .LeftMargin = Korlevel.PageSetup.LeftMargin 
            .RightMargin = Korlevel.PageSetup.RightMargin 
            .Gutter = Korlevel.PageSetup.Gutter 
        End With

        Selection.PasteAndFormat Type:=wdFormatOriginalFormatting

Azt, hogy a formázás ne változzon, két beállítással tudjuk elérni. Egyrészt másolás előtt beállítjuk a margókat úgy, hogy azok megegyezzenek az eredeti körlevél dokumentum margó beállításaival. Másrészt nem a .Paste metódust használjuk, mert ez esetben a formázást (betűtípus, betűméret, stb.) nem másolja, hanem az újonnan létrehozott dokumentum beállításait alkalmazza, hanem a .PasteAndFormat metódust, ahol megadhatjuk, hogy tartsa meg az eredeti formázási beállításokat.

Az új dokumentumban a szekciótörésre már nincs szükségünk, így töröljük azt:

        Selection.MoveLeft Unit:=wdCharacter, Count:=1
        Selection.Delete Unit:=wdCharacter, Count:=1

Vagyis a kurzort a .Left utasítással egy karakternyit balra léptetjük, majd a .Delete utasítással töröljük a kurzor mögötti karaktert (ami nem más mint a szakasztörés jel).

Ezután mentsük el az új dokumentumot a megfelelő könyvtárba a megfelelő néven:

	ChangeFileOpenDirectory Korlevel.Path
        FajlNev = CimAdas(TempDoc)
        TempDoc.SaveAs FileName:=FajlNev & ".docx"

ChangeFileOpenDirectory metódussal beállíthatjuk a mentés könyvtárának elérési útvonalát (ne tévesszen meg senkit, hogy az utasításban “Open” szerepel…), ami a példánkban megegyezik a körlevél dokumentum könyvtárával. A dokumentum elnevezéséhez pedig jelen esetben egy függvényt (CimAdas) használunk (lásd lejjebb).

Amennyiben Pdf-ként is menteni szeretnénk a levelet, azt egy külön szubrutin segítségével végezhetjük el, amit a levél lezárása előtt meghívhatunk:

        SavePdf

Végezetül zárjuk le az elmentett dokumentumot (most már újabb mentés nélkül):

        TempDoc.Close savechanges:=wdDoNotSaveChanges

A teljes eljárás egyben így néz tehát ki:

Option Explicit
Dim FajlNev As String
Dim Korlevel As Document
Dim TempDoc As Document

Sub Korlevelbol_Egyedi_Levelek()
    
Dim i As Long

Set Korlevel = ThisDocument

    Application.Browser.Target = wdBrowseSection

    For i = 1 To ((Korlevel.Sections.Count) - 1)

        Korlevel.Bookmarks("\Section").Range.Copy

        Set TempDoc = Documents.Add

        With TempDoc.PageSetup
            .TopMargin = CentimetersToPoints(2.25)
            .BottomMargin = CentimetersToPoints(2)
            .LeftMargin = CentimetersToPoints(2)
            .RightMargin = CentimetersToPoints(2)
            .Gutter = CentimetersToPoints(0)
        End With

        Selection.PasteAndFormat Type:=wdFormatOriginalFormatting

        Selection.MoveLeft Unit:=wdCharacter, Count:=1
        Selection.Delete Unit:=wdCharacter, Count:=1

	ChangeFileOpenDirectory Korlevel.Path
        FajlNev = CimAdas(TempDoc)
        TempDoc.SaveAs FileName:=FajlNev & ".docx"
        
        SavePdf
        
        TempDoc.Close savechanges:=wdDoNotSaveChanges

        Application.Browser.Next
    Next i    
    
End Sub

Fájlok elnevezése

Mail Merge Address boxAhogy a bevezetőben olvasható, példánkban a levelek címzéssel kezdődnek, az első szöveges sorban szerepel a levél címzettje, akinek a nevét szeretnénk a fájl neveként felhasználni. Az első szöveges sor nem feltétlenül a dokumentum első sora, a dokumentum kezdődhet sortörésekkel: ennek programozáskor lesz jelentősége.

Function CimAdas(Doc As Document) As String

Dim i As Long

    i = 0
    Do
        i = i + 1
        CimAdas = Doc.Paragraphs(i).Range.Text
    Loop While Len(CimAdas) <= 1

        With Doc
            CimAdas = Left(CimAdas, Len(CimAdas) - 1)
            CimAdas = Replace(CimAdas, "\", "")
            CimAdas = Replace(CimAdas, "/", "")
            CimAdas = Replace(CimAdas, ":", "")
            CimAdas = Replace(CimAdas, """", "")
            CimAdas = Replace(CimAdas, vbCr, "")
            CimAdas = Replace(CimAdas, vbTab, "")
        End With

End Function

A függvényünk két részből áll. Az első részben egy Do … Loop ciklussal megkeressük az első szöveges sort (a pontosság kedvéért: az első olyan sort, ami egy karakternél hosszabb, ahol az egy karakteres sorok sortöréseket jelentenek), ez lesz a fájlnevük kiinduló állapota. Ezután a Replace függvény segítségével törlünk (helyettesítünk) minden olyan karaktert, ami a fájlnévben nem használható.

Pdf készítése

A megnyitott Word dokumentumot Pdf-ként elmenteni – jelen példánkban – az ExportAsFixedFormat metódussal fogjuk:

Sub SavePdf()

    TempDoc.ExportAsFixedFormat _
        OutputFileName:=FajlNev & ".pdf", _
        ExportFormat:=wdExportFormatPDF, _
        OpenAfterExport:=False, _
        OptimizeFor:=wdExportOptimizeForPrint, _
        Range:=wdExportAllDocument

End Sub

Itt paraméterként – többek között – megadhatjuk a fájl nevét, formátumát és hogy az egész dokumentumot exportáljuk-e (lehetőség van egy-egy oldal, vagy meghatározott kijelölés exportálására is).

39 thoughts on “Hasznos kiegészítő Word körlevélhez

  1. Nahát! Ez pontosan az,amit kerestem,nagyon köszönöm! Érthető és hasznos! 🙂
    Egyetlen kérdésem lenne. Megoldható,hogy a fájl neve más legyen? Mondjuk az aktualis dátum és a dokumentum egy adott pontján lévő szöveg?
    Pl a tárgya a dokumentumnak (figyelmeztetés,gratuláció,stb) ami a levélben szerepel valahol?
    Nagyon szépen köszönöm!

    1. Kedves István, először is üdvözlöm a blogon!
      A válasz egyértelműen igen. A megvalósítás attól függ, hogy a dokumentumon belül hogyan tudjuk azonosítani a keresetett szüveget.
      A példámban a Do…Loop ciklus az első szöveges sort keresi meg, és az lesz a cím. Ha viszont, ettől eltérően, például a dokumentum tulajdonságai között szereplő címét szeretnénk használni, akkor a BuiltInDocumentProperties attributum körül kell keresgélni. Ha például az oldalon ‘Heading1’-ként formázott szöveg tartalmazza a keresett szót, akkor formázásra lehet keresni. Ha van egy listánk a keresett kifejezésekről, és feltételezzük, hogy a dokumentum ebből csak egyet tartalmaz, akkor a listára lehet futtatni egy keresést (lásd a “Továbbfejlesztett keresés” című bejegyzésemet, azt kis átalakításokkal át lehet ültetni Word-re). Vagy kereshetünk a “Tárgy:” kifejezésre, és mondhatjuk azt, hogy minden, ami ezután szerepel a sorban, az lesz a fájlnevünk.
      Az aktuális dátumot pedig a Date függvénnyel lehet előhívni, csak a formátum átalakításáról (pl “yyyymmdd”, vagy amit szeretnénk) kell gondoskodni.

  2. Tisztelt Kántor Ádám!

    Körlevél készítésénél tapasztaltam, hogy:
    a forrás állományból (excel) kiválasztott cellában több mint 256 karakter (pl. 1185 db) van és a körlevélben csak 255 darab karakter jelenik meg.
    Mi lenne a jó megoldás?

    Köszönettel,
    tapai

    1. Kedves József!
      Üdvözlöm a blogon!
      Amikor a Word adatokat húz be Excelből (Accessből, stb…), akkor a mező adattípusát az első néhány mező alapján határozza meg (talán első 8, de ebben nem vagyok biztos). Ha ebben a néhány mezőben nincs 255 karakternél hosszabb szöveg, akkor az adattípust “Text” (vagy “Short Text”)-nek veszi, ami maximum 255 karakter lehet, így a későbbi mezőket is megvágja 255 karakternél. Ha az első mezők között szerepel hosszabb szöveg, akkor “Long Text” (régebbi elnevezésén “Memo”) adattípust állít be, ami – ha jól emlékszem – 1 gigabájtnyi szöveget enged.
      Amit én alkalmazni szoktam, hogy a forrás excel tábla első rekordját dummy adatokkal töltöm fel (érdemes figyelni az adattípusra, tehát ha egy oszlop csak egész számokat tartalmaz, akkor a dummy adat is egy egész szám legyen), ahol a kérdéses oszlopba egy jó hosszú szöveget kell írni (például 256 darab x-et). A körlevél beállításainál pedig be lehet állítani, hogy az első rekordból ne készítsen levelet.

      1. nagyon köszönöm, ezek szerint fogok eljárni
        a blogot tanulmányozni fogom, mert tetszik
        üdv,
        tapai

      2. Kedves Ádám! Nagyon köszönöm, ez annyira hasznos volt, hogy azt elmondani nem tudom. Lehet, hogy már nem is működik ez az oldal (elég régiek a bejegyzések), de muszáj megírnom, milyen hálás vagyok (pláne, hogy kiderült, nem én vagyok a teljesen oktondi :-)).
        Köszönöm a segítséget!
        Andrea

        1. Kedves Andrea! Örülök, ha így látja, köszönöm a visszajelzést! És köszönöm a célzást, igyekszem újra időt keríteni, hogy újra működőnek látszódjon az oldal, témám akadna bőven 🙂

          1. Én pedig (másokkal együtt) sok új, hasznos infóhoz juthatnék :-). Részemről várom!

  3. Kedves Ádám!
    Ha egyesítem a körleveleket akkor az új “levél1.doc” file már nem látja a makrót. Ezt hogy lehet meghivatkozni.
    Köszönöm,

    1. Kedves Márton, köszöntöm a blogon!
      A leírásából nem egyértelmű nekem, hogy pontosan mi okozza a problémát, ezért nem tudom, sikerül-e megválaszolnom.

      Ha arra gondolt, hogy miután a sok levelet tartalmazó fájlt a makró segítségével feldarabolta (például levél1.doc, levél2.doc, levél3.doc, stb. elnevezésű fájlokra), hogyan lehet az egyes levelekkel további műveleteket végezni, akkor a hivatkozás a lentiek szerint működik:
      Feltételezve, hogy tempDoc nevű változóba mentjük az objektumhivatkozást (Dim tempDoc As Document)
      – ha már meg van nyitva a fájl, akkor:
      Set tempDoc = Documents(“levél1.doc”)
      – ha még nincs nyitva, akkor a hivatkozáshoz és további műveletekhez így nyitható meg:
      Set tempDoc = Documents.Open(“C:\temp\levél1.doc”)

      Ha az összes lementett dokumentummal el szeretnénk végezni egy adott műveletet, akkor érdemes lehet erre a célra létrehozni egy gyűjteményt (collection), és az egyedi fájl mentése után eltárolni a hivatkozását ebbe, majd egy iterációval egyenként meg lehet nyitni a fájlokat és elvégezni rajutuk a kívánt műveleteket. Alternatívaként még az egyedi fájl bezárása esetén el lehet rajta végezni a műveleteket (a blogbejegyzésben szereplő példában is TempDoc-ként hivatkozunk az egyedi fájlra).

      1. Kedves Ádám!
        Egy példa:
        Van egy körlevél_sablon.doc állomány (ebbe van a makró) + a hozzá tartozó körlevél_sablon.xlsx állomány. Ha a sablont megnyitom az excel fileból behúzza az adatokat a word-be. Ez után ha a befejezés és egyesítés gombra kattintok akkor létrejön egy levél1 dokumentum. Na ebből a fileból nem tudom futtatni a makrót, mert nem látja. E miatt el sem jutok a darabolásig.
        Köszönöm válaszát.

        1. Kedves Márton,
          Ha jól értem, maga a körlevélkészítés még kézzel történik, nem makróval, és a sablon fájlban lévő makrót a körlevél elkészülte után szeretné futtatni.
          Ez esetben is lehet a makrót a sablon fájlból futtatni, a blogbejegyzés elején szereplő hivatkozást kell cserélni –
          erről:
          Set Korlevel = ThisDocument

          -ha a fájl már el lett mentve levél1.doc néven és nyitva van:
          Set Korlevel = Documents(“levél1.doc”)
          -ha még nincs elmentve ÉS FELTÉTELEZVE, hogy más fájlt nem nyitottunk meg a körlevél generálása óta:
          Set Korlevel = Documents(Documents.Count)
          ez utóbbi utasítás az utolsóként megnyitott/létrehozott fájlra hivatkozik a ‘Korlevel’ változóval.

          1. Kedves Ádám!

            Kaptam egy újabb dokumentumot, amin sajnos a fenti kódot az alábbi okból nem tudom alkalmazni. A dokumentum tartalmaz egy táblázatot is ami fektetett fomában van a wordben. Emiatt nem egy hanem 3 szakasztörést eredményez dokumentumonként.

            Hogyan tudom megmondani a vba-nak, hogy minden 3-nál tördelje a körlevelet?

            Köszönöm,
            Marci

          2. Kedves Marci!

            A leírás alapján feltételezem, hogy a táblázat elé és mögé egy ” Section Break (Continuous) ” típusú elválasztót rak be, míg az oldaltörésekhez ” Section Break (Next Page) ” típusút; a blogbejegyzésben leírtak ezt tényleg nem figyelik, minden Section Break elválasztót egyenrangúnak vesz. Én kétféle megközelítést próbálnék ki, mindkettőnél a For…Next cikluson belülre kell rakni még egy feltételvizsgálatot, vagyis a
            For i = 1 To ((Korlevel.Sections.Count) - 1)” sor után:

            1. kód végrehajtása minden harmadik elválasztónál
            Ez esetben az i számlálót vizsgáljuk, ha osztható hárommal, akkor végrehajtjuk a műveleteket, ha hamis, akkor továbblépünk a következő szekcióra; ezt a “mod” operátorral tehetjük meg.

            For i = 1 To ((Korlevel.Sections.Count) - 1)
            If i mod 3 = 0 Then
            'Műveletek
            End If
            Application.Browser.Next
            Next i

            2. kód végrehajtása csak a “Next Page” típusú elválasztóknál
            Ez esetben azt vizsgáljuk, hogy az elválasztó típusa milyen, és csak akkor hajtjuk végre a műveleteket, ha az “Next Page” típusú.

            For i = 1 To ((Korlevel.Sections.Count) - 1)
            If Korlevel.Sections(i).PageSetup.SectionStart = wdSectionNewPage Then
            'Műveletek
            End If
            Application.Browser.Next
            Next i

  4. Kedves Ádám! Ismét egy probléma, amelyet saját kútfőből nem tudok megoldani, kérem, segítsen (Ön biztos tud :-)).
    Excel táblázat, ebből egy word körlevél dokumentum készül. Az adatmezőkkel nincs problémám, de a körlevélbe képeknek is be kellene húzódniuk (ráadásul körlevelenként más-más képnek, és nem is egynek) . Azért a feltételes mód, mert ez az, amit nem tudok megoldani.
    Az excel tartalmazza azt az oszlopot, ahova a képek elérési helyét belinkeltem. A word körlevél tartalmazza azt az adatmezőt, ahol a képnek meg kellene jelennie (beszúrás, kész modulok, mező, insertpic, stb). És itt megáll a tudomány, mert a kép helyén csak egy piros X látszik az alap körlevél dokumentumban is, és összefűzés után is. Könnyen lehet, hogy már az alapdokumentum is rossz, és valahogy máshogy kellene csinálni. Tud segíteni, hogy hogyan lehet ezt megoldani?
    Köszönöm szépen előre is!
    Andrea

  5. Kedves Ádám!
    Szakértelmed és segítséged reményében fordulok hozzád problémámmal. Az excelben kalkulált adatokat körlevél szerkesztő segítségével szeretném word-ben szövegkörnyezetben megjeleníteni. Ehhez több mint 255 különböző mezőre lenne szükségem, azonban a word csak az első 255 darab mező (oszlop) beszúrását engedi. Az ezen túliak nem jelennek meg a mező beszúrásakor, úgy mintha nem is lennének.
    Hogy lehetne több mezőt használni?
    Segítséged előre is köszönöm!

    1. Kedves Misi!
      A 255 mezős korlátozás a Microsoft adatbázis motorjának a sajátja, ezt tudtommal nem lehet kikerülni: akkor sem, ha Excel helyett valami más adatbázist használnánk, és akkor sem, ha körlevél helyett programatikusan próbálnánk adatokat lekérdezni adatbázisból (pl. ADO API-n keresztül SQL-el).

      Az egyik irány, ami eszembe jutott, hogy csökkenteni lehetne a körlevél által használt mezők számát. Egy egyszerű példával: ha a táblában külön-külön mezőink vannak az irányítószámnak, városnak, utcának, házszámnak, abból összefűzéssel még az Excelben lehet képezni egy cím mezőt, és a körlevélbe ezt az egy cím mezőt lehet beolvasni. De ez akár komplett mondatokra, bekezdésekre is működhet, segédtáblákkal, kreatív képletezéssel (feltétel vizsgálatok, behelyettesítések, összefűzések, sortörés karakterek beillesztése, stb.) egész dinamikus szöveg blokkokat lehet már az Excelben gyártani, és azokat egy mezőként a körlevélbe berakni.

      A másik irány, amit én használni szoktam, hogy programmal oldjuk meg az egész feladatot, a körlevélkészítést teljesen kikerülve. Ehhez először egy sablon Word fájlt kell készíteni, abba szövegként berakni a placeholdereket (pl.: %%mezo_1%%). A programnak nagy vonalakban ezt kell tennie:

      • Iteráció a forrás tábla rekordjain. Minden egyes rekordhoz:
      • Új .docx fájl nyitása (vagy igény szerint új oldal/szekció kezdése), tartalom másolása a sablon fájlból.
      • Iteráció a forrás tábla mezőin. Minden egyes mező érték vonatkozásában:
      • Összes hozzá tartozó placeholder megkeresése és szöveg cseréje
      • Egyedi Word fájlok gyártása esetén fájl mentése, bezására

      Ez utóbbi megoldás előkészítése azért időigényesebb, mint a körleveles megoldásé, de sokkal kreatívabban használható: többféle template-ből dinamikusan válogathat, képeket vagy egyéb tartalmakat letet beilleszteni, változó méretű táblázatokat lehet vele gyártani, további formázásokat, kalkulációkat lehet elvégezni.

  6. Kedves Ádám!
    Olvasgatva a fórumot, nagyon sok hasznos dolgot találtam, és tanultam is belőle.
    Jelenlegi feladatomnál sajnos makro nem megoldható, mivel többféle kimutatásból, többféle változó mezőből kell pár körlevelet összetenni, ahol a mezők nevei változnak.
    Ez összesen jelenleg 3 ilyen kimutatás van.
    Ezeket jelenleg még egyszerűbb kézzel megcsinálni.
    Ami a problémát okozza, hogy grafikus megjelenítést is át kellene emelnem WORD körlevélre, viszont amit találtam plug-int “Merge Tools” valami hiba miatt folyamatosan megáll memória címzési hiba miatt.
    Az lenne a kérdésem, hogy van-e valamilyen egyszerűbb megoldás a grafikon körlevélbe való átemeléséhez?
    Köszönettel:
    Gyuri

    1. Kedves Gyuri!
      Grafikonok körlevélbe ágyazásával kapcsolatban sajnos nincs gyakorlati tapasztalatom, úgyhogy amit meg tudok osztani az, hogy én milyen irányban indulnék el. De ha jól értem, két megoldandó téma van: többféle változó mező és grafikon.

      A Merge Tools programot nem ismerem, de a készítője azt írja, hogy probléma esetén kapcsolatba lehet vele lépni: egy próbát megérne a dolog. Ezen a linken van egy jpg fájl, ott van egy email cím, én azon próbálkoznék először.

      A többféle változó mező kapcsán nem lehet megoldás egy segédtábla készítése, ahová a 3 kimutatásból behúzod az adatokat, és onnan már mehet a körlevél egységes mezőnevekkel?

      Ha a fentiek nem működnek, én azért megpróbálnám makróval. Egy megoldás lehet, ha nem is a legegyszerűbb, hogy a Word körlevél funkciója helyett “emuláljuk” annak működését. Ennek egyik (talán egyszerűbb) része, hogy a szükséges adatokat beolvassuk a 3 kimutatásból (memóriába / változóba), a másik (bonyolultabb) része, hogy az adatok alapján egy/több Word fájlt generálunk – ez utóbbihoz azért el kell merülni a Word objektumkönyvtár használatában, de működik a gyakorlatban. Grafikonok átemelését ezzel a módszerrel sem próbáltam még, de biztos vagyok benne, hogy le lehet programozni.

  7. Kedves Ádám,
    Köszönöm a szenzációs makrót, sikerült a darabolás és átnevezés is. Ami problémát jelentett (ebben kérném a segítséged) az, hogy a makrót hova mentsem. Ha a normal dotm-ba mentette a rendszer, akkor működött. Ha viszont csináltam egy körlevél.dotm dokumentumot, ami a makrót tartalmazta és mellé megnyitottam a darabolandó kész körlevelet (kész.cocx), akkor a következő hibaüzenetet írta ki: A gyűjtemény kívánt tagja nem létezik. Mindezt a Korlevel.Bookmarks(“\Section”).Range.Copy sornál követte el.
    Az előtte lévő sorok így néznek ki:
    Set Korlevel = Documents(“Kész.docx”)
    Application.Browser.Target = wdBrowseSection
    For i = 1 To ((Korlevel.Sections.Count) – 1)
    Korlevel.Bookmarks(“\Section”).Range.Copy

    Válaszodat nagyon várom és előre is köszönöm.

    1. Kedves László!

      Ha jók a sejtéseim, a hibaüzenetet csak áttételesen befolyásolja az, hogy hová van mentve a makró. A hibát okozó sor, azon belül is a Bookmarks("\Section") hivatkozás csak az aktív dokumentumban működik. Amikor futtattad a makrót, minden valószínűséggel a körlevél.dotm fájl volt az aktív, nem a kész.docx fájl. A Set Korlevel = Documents("Kész.docx") sor után illessz be még egy sort:


      Korlevel.Activate

      ez aktiválja a Kész.docx dokumentumot, és ez nálam meg is oldotta a problémát.

      A mentés helyével kapcsolatban: én a .docm fájlnak egy standard moduljába raknám a makrót.

  8. Kedves Ádám!
    Persze, ez eszembe juthatott volna.
    Még annyit kérdeznék:
    Van az élőfejben egy logó és egy enter
    A szövegben 8 klf bekezdés van enterrel lezárva, majd jön egy
    Név: TAB jel, majd a Dolgozó_ neve_korlevmező sor.
    Innen kéne a dolgozó nevét kiszednem, mert ebből képzem a fájlnevet fenti leírásod alapján.
    Hogyan tudom ezt megtenni. Úgy működött, hogy az első sorban volt a dolgozó neve.
    Köszönettel László

    1. Kedves László!
      Amiatt nem egyszerű a dolog, mert miután kész, adatokkal kitöltött körlevél dokumentum elkészült, ebben a dokumentumban már nincsenek benne a körlevél készítéshez használt mezők, így azok alapján nem lehet egyszerűen keresni.

      Két megoldás jutott eszembe, amik működhetnek, ha bizonyos feltételek teljesülnek.

      Az első a blogbejegyzésben szereplő névadás továbbfejlesztése: itt nem az első szöveges sort keressük meg, hanem az első sort, ami úgy kezdődik hogy “Név:” (a Loop While Len(CimAdas) <= 1 sor helyett lehet például Loop While Instr(1, CimAdas, "Név:", vbTextCompare) < 1), majd ebből kinyerjük a dolgozó nevét (vagyis a "Név:" utáni karaktereket).

      A második kód szempontjából kicsit bonyolultabb (legalábbis VBA-ban), de univerzálisabban használható (amire jelen esetben nem biztos hogy szükség van, de máskor jól jöhet): RegEx (Regular Expression) használata. Nem tudom, találkoztál-e már RegEx-szel, ha nem, akkor ez az oldal egy egész jó kis áttekintést ad (én is Google-ből kerestem most, biztos létezik még sok hasonló): RegEx. Illetve egy általam sokat használt oldalt tudnék még RegEx témában ajánlani: regex101.com - itt lehet tesztelni, próbálgatni.

      Feltételezve, hogy a "Név:" csak egyszer szerepel a dokumentumban, és a név sorban a dolgozó neve után már nem szerepel semmi, a kapcsolódó függvény valahogy így nézhet ki:

      Public Function DolgozoNeve() As String
          Dim s as String
          s = ActiveDocument.Range.Text 'ActiveDocument helyett a mentendo dokumentum hivatkozasa
      
          Dim re As RegExp
          Set re = New RegExp
          
          re.Pattern = "Név:\t([\w\s]+\w)$"
          re.Global = True
          re.MultiLine = True
          
          Dim Matches As MatchCollection
          Set Matches = re.Execute(s)
          
          Dim m As Match
          For Each m In Matches
              Dim n_groups As Long        
              n_groups = m.SubMatches.Count
              If n_groups >= 1 Then
                  DolgozoNeve = Trim(m.SubMatches.Item(0))
                  Exit Function
              End If
          Next m
      
      End Function
      
  9. Kedves Ádám,
    A körlevél sikeresen elkészült, köszönet a sok segítségért

  10. Tisztelt Szerző!

    2025-öt írunk
    Windows 11 és Ofice LTSC Professional Plus 2021 használok
    Pont a megadott makróra lenne szükségem
    Bemásoltam egy önálló modulba.
    Ugyan nem fut hibára de nem is csinál semmit. Hol kell aktualizálni?

    Köszönöm

    1. Kedves József!

      Így újra átfutva a kódot, nem tűnt fel semmi, ami ne lenne kompatibilis az Office 2021-es verziójával.

      Amit nulladik lépésben ellenőriznék, hogy minden használt modul elején szerepel-e az Option Explicit utasítás (ez biztosítja, hogy a hibák egy jó részét már a compiler megtalálja).

      Ha ezután is lefut a program, de hibaüzenet nélkül, akkor a következő gyanúm, hogy a dokumentumban nincsenek Section Break-ek –> ezt az alapból rejtett formázó szimbólumok megjelenítésével lehet ellenőrizni.

      Ezen felül látatlanban nehéz bármit mondani, a kódon debug mode-ban végiglépdelve ellenőrizném a kritikus részeket (pl. látja-e a szekciókat, vagyis a Korlevel.Sections.Count változó 1-nél nagyobb számot mutat, és belép-e az iterációba; ha igen, akkor az iteráción belül legenerálja-e az új dokumentumot, tud-e neki egyedi nevet generálni, illetve maga a mentés sikeres-e).

  11. Köszönöm a gyors választ.
    Igazából az alap problémát az elégtelen MS Word makró ismeretem adja.
    Fogtam a megadott makrót és bemásoltam egy Modulba. Hibát jelzett ezért ugyanezen modulba legalulra beillesztettem a Fájlok elnevezése és a Pdf készítése makró részeket
    Ezek elválasztó vonallal vannak “elszeparálódva” egymástól
    Elindítva nem jelez hibát de igazából nem is történik semmi érdemleges. F8 al lépésenkénti végrehajtással eljut a FOR i = 1 … sorig majd a következő F8 ra leugrik az END SUB sorra. A közbenső utasítások mintha ott sem lennének.

    Köszönőn ha segítségemre lesz

    1. A leírtak alapjánaz a helyzet állhat fent, hogy nincsenek szekció elválasztók a dokumentumban. Ha a Word körlevél (Mail Merge) funkciójával készítette a dokumentumot, akkor az egy többoldalas dokumentum, ahol egy-egy címzett levele 1-1 oldalt foglal egy (vagy lehet akár több oldal is levelenként), és az egyedi levelek Section Break-el vannak elválasztva.

      Section Break

      Ez elvileg nem változott, most Office365-el kipróbálva is Section Break elválasztókkal gyártotta le a Word a körlevelet.

      Ha esetleg a körlevél másképp készült, akkor a Section Break-ek hiányozhatnak, viszont ezek megléte szükséges, hogy a makró működjön.

      (Elméletileg az oldalankénti szétszedés sem lehetetlen, viszont jóval bonyolultabb, mivel az oldal, mint olyan, nem egy egzakt objektum a Word-ön belül, mert az hogy mennyi szöveg van egy oldalon, függ a margóktól, betűmérettől, nyomtató beállítástól, sortörésektől, oldaltörésektől, oszloptörésektől, stb., úgyhogy azt a Word dinamikusan számolja. Ezzel ellentétben a Section Break-ek konkrét objektumok, pontos pozcióval.)

      1. Nem tudok képet beilleszteni.
        Szakasztörés (új oldal)
        vannak elválasztva

        önálló makró modulba futtatva az Application.Browser.Next utasítás működik

        Ciklusba rakva ha fix darabot kérek – jelen esetben 12 db – akkor lefut az ugrás
        viszont a ((Korlevel.Sections.Count) – 1) már hibára fut

        Option Explicit
        Dim FajlNev As String
        Dim Korlevel As Document
        Dim TempDoc As Document
        Dim i As Long

        Sub Makró1()

        ‘ Makró1 Makró
        ‘ próba

        For i = 1 To ((Korlevel.Sections.Count) – 1)
        Application.Browser.Next
        Next i
        End Sub

        Error 91
        Object variable or with block variable not set

        Ellénézést az amatőr kérdések miatt. Nulla WORD makró ismeretein vannak.

  12. Kedves Szerző!

    Ha fix db számra állítom akkor lefut szépen.
    Set Korlevel = ThisDocument
    For i = 1 To 12
    Application.Browser.Next
    Next i
    End Sub

    Ha beállítom a ((Korlevel.Sections.Count) – 1) számlálónak akkor a For után azonnal az End Sub ra ugrik
    Set Korlevel = ThisDocument
    For i = 1 To ((Korlevel.Sections.Count) – 1)
    Application.Browser.Next
    Next i
    End Sub

    1. Az Application.Browser.Next alapból a következő oldalre ugrik, nem a következő szakaszra. Ahhoz, hogy a szakaszok között ugráljon, előtte át kell állítani a Target-et: Application.Browser.Target = wdBrowseSection

      Ha lefuttatja a lenti kódot, milyen számot dob ki?

      Sub Test()
      MsgBox ThisDocument.Sections.Count
      End Sub

      Ez a szakaszok számát olvassa ki, ami egyenlő a szakasztörések száma +1-el.
      Ha ez 1-et ad ki, akkor valami miatt a szakasztöréseket nem tudja értelmezni (pedig az Ön által írt “Szakasztörés (új oldal”) az általam írt “Section Break (Next Page)” magyar megfelelője).

    2. Még egy valami beugrott, ami okozhatja a jelenséget.
      A kód szerkesztőben két hely van, ahová makrót lehet írni.
      A struktúra valahogy így nézhet ki (a megnevezések lehetnek mások):

      +-Normal
      |˙˙˙|--Microsoft Word Objects
      |˙˙˙|--Modules
      |˙˙˙˙˙˙|--Module1
      +-Project(Document1)
      ˙˙˙|--Microsoft Word Objects
      ˙˙˙|--Modules
      ˙˙˙|˙˙|--Module1
      ˙˙˙|--References

      Ahhoz, hogy a makró a jelenleg nyitott dokumentumra vonatkozzon, nem a “Normal” alatti modul(ok)ba, hanem a projekt (nálam Project) alatti modul(ok)ba kell írni.
      Ez még megér egy ellenőrzést.

      1. EZ AZ!

        Köszönöm. Itt lesz a kutya elásva. Elindul, le is képezi az első .DOCX állományt. De a .PDF -nél elakad Variable not defined el.
        OutputFileName:=FajlNev & “.pdf”, _
        Mire kellene definiálni?

        Bocsi 0% Word makró ismereteim vannak.
        ÉS köszönö a segítséget

        1. Eddig eljutottam :
          Option Explicit
          Dim FajlNev As String
          Dim TempDoc As Document

          Erre
          ObJECT VARIABLE OR WITH BLOCK VARIABLE NOT SET
          hibaüzenet
          Valami nincs beállítva. De mi és hol?

          1. Jelen példában a Dim FajlNev As String és Dim TempDoc As Document-nek eljárásokon kívül kell lenniük (ha közvetlenül az Option Explicit alatt vannak, akkor az jó), és valahol a cikluson belül kell lennie a Set TempDoc = Documents.Add sornak, illetve szintén a cikluson belül kell lennie a SavePdf sornak.

            Ezen kívül, a cikluson belül kell lennie a sornak, ami meghatározza a nevet (a példámban FajlNev = CimAdas(TempDoc)), itt debug mode-ban max azt érdemes megnézni, hogy valid nevet kapunk-e vissza a CimAdasfüggvényből.

            Illetve, nem tudom Önnél be van-e kapcsolva az automatikus szintaxis ellenőrzés, de futtatás előtt a VBA szerkesztőből érdemes kézzel lefuttatni: Debug >> Compile VBA Project
            Ez kidobja, ha esetleg valami gépelési hiba maradt benne, vagy valamelyik változó nincs definiálva (mert például nem jó scope-ban definiáltuk).

  13. Sub Test()
    MsgBox ThisDocument.Sections.Count
    End Sub

    Eredmény 1

    Jó lenne ha képernyő videot lehetne csatolni.

  14. Tisztel Szerző!

    Eddig jutottam el. Az alábbi makróval. A PROJECT egyedi file MODULES ból futtatva Korlevel.docm mentett állományban.

    Module1:
    Option Explicit
    Dim FajlNev As String
    Dim Korlevel As Document
    Dim TempDoc As Document

    Sub Korlevelbol_Egyedi_Levelek()

    Dim i As Long

    Set Korlevel = ThisDocument

    Application.Browser.Target = wdBrowseSection

    For i = 1 To ((Korlevel.Sections.Count) – 1)

    Korlevel.Bookmarks(“\Section”).Range.Copy

    Set TempDoc = Documents.Add

    With TempDoc.PageSetup
    .TopMargin = CentimetersToPoints(2.25)
    .BottomMargin = CentimetersToPoints(2)
    .LeftMargin = CentimetersToPoints(2)
    .RightMargin = CentimetersToPoints(2)
    .Gutter = CentimetersToPoints(0)
    End With

    Selection.PasteAndFormat Type:=wdFormatOriginalFormatting

    Selection.MoveLeft Unit:=wdCharacter, Count:=1
    Selection.Delete Unit:=wdCharacter, Count:=1

    ChangeFileOpenDirectory Korlevel.Path
    FajlNev = CimAdas(TempDoc)
    TempDoc.SaveAs FileName:=FajlNev & “.docx”

    Rem SavePdf

    TempDoc.Close savechanges:=wdDoNotSaveChanges

    Application.Browser.Next
    Next i

    End Sub

    Module2:
    Option Explicit

    Function CimAdas(Doc As Document) As String

    Dim i As Long

    i = 0
    Do
    i = i + 1
    CimAdas = Doc.Paragraphs(i).Range.Text
    Loop While Len(CimAdas) <= 1

    With Doc
    CimAdas = Left(CimAdas, Len(CimAdas) – 1)
    CimAdas = Replace(CimAdas, "\", "")
    CimAdas = Replace(CimAdas, "/", "")
    CimAdas = Replace(CimAdas, ":", "")
    CimAdas = Replace(CimAdas, """", "")
    CimAdas = Replace(CimAdas, vbCr, "")
    CimAdas = Replace(CimAdas, vbTab, "")
    End With

    End Function

    Module3:
    Option Explicit
    Dim FajlNev As String
    Dim TempDoc As Document

    Sub SavePdf()

    TempDoc.ExportAsFixedFormat _
    OutputFileName:=FajlNev & ".pdf", _
    ExportFormat:=wdExportFormatPDF, _
    OpenAfterExport:=False, _
    OptimizeFor:=wdExportOptimizeForPrint, _
    Range:=wdExportAllDocument

    End Sub

    A kívánt eredményt adja. Az első sorban lévé szöveg adja a file nevét. Annyi állományt ment amennyi egyedi körlevél képződik.
    A .PDF mentés nem fut le Object variable or With block variable not set hibaüzenettel. Igy ezt REM be raktam.

    A kérésem az lenne. Hol és mit kellene átírni, hogy a NORMAL ba is fusson, hogy a NORMAL.DOT ba is menthető legyen és álltalánosan működjön?
    A körlevél egyesítése után Levél1 nevű állományt kapok. Ebben kellene a makró futtatását indítani. Önálló névvel .DOCX és .PDF állományokban.

    Köszönök minden eddigi és jövőbeni segítséget.

    1. A pdf mentéssel kapcsolatban:
      Ha külön modulokba szétszedi az eljárásokat, akkor a modul szinten (Option Explicit alatt) deklarált változók nem lesznek elérhetők egymás számára. Ezért nem fut a SavePdf eljárás, mert sem a dokumentum, sem a fájlnév nem létezik számára, csak üres változók. Ez könnyen orvosolható, ha paraméterként átadjuk neki:

      Module 3:

      Option Explicit

      Sub SavePdf(ByVal FajlNev As String, ByVal Doc As Document)
      Doc.ExportAsFixedFormat _
      OutputFileName:=FajlNev & ".pdf", _
      ExportFormat:=wdExportFormatPDF, _
      OpenAfterExport:=False, _
      OptimizeFor:=wdExportOptimizeForPrint, _
      Range:=wdExportAllDocument

      End Sub

      A Module1-ből pedíg így kell meghívni:
      SavePdf FileNev, TempDoc

      Ahhoz, hogy NORMAL.DOT alól is fusson, szerintem egy dolgot kell változtatni:
      Set Korlevel = ThisDocument helyett Set Korlevel = ActiveDocument.
      Értelemszerűen ez esetben a makró akkor fog rendben futni, ha a feldolgozandó körlevél fájl az épp aktív dokumentum.

Vélemény, hozzászólás?

Az e-mail-címet nem tesszük közzé.

This site uses Akismet to reduce spam. Learn how your comment data is processed.