scelta stampante condivisa macro excel



  • scelta stampante condivisa macro excel
    di roberto21 data: 27/06/2017 20:14:21

    Eccomi di nuovo qui. La modesta utility excel per stampare le ricevute è destinata ad essere usata da almeno tre utenti diversi. Non simultaneamente, ovviamente, altrimenti la numerazione consecutiva avrebbe dei problemi. Così il tutto è installato su un disco condiviso e la stampa dovrebbe avvenire su una stampante dedicata collegata allo stesso pc che fa da server del disco (\UTENTE1...). Spulciando qua e là, ho capito che questa stampante, installata sui tre pc in questione, deve essere individuata col suo nome completo, che risulta diverso per ogni computer: ho visto che il codice che mi serve va a prendere il nome della stampante dall'elenco contenuto nel registro di sistema,

    HKEY_CURRENT_USERSOFTWAREMicrosoftWindows NTCurrentVersionDevices

    per cui p.es. il pc1 userà \UTENTE1Samsung 1860 series on NE11:, il pc2 troverà \UTENTE1Samsung 1860 series on NE03: etc. Il codice è quello riportato sotto: me lo sto studiando, ma la sostanza è abbastanza chiara: passo alla funzione GetPrinterFullName la stringa "Samsung", e la funzione esamina una per una le voci della entry del registro e mi rimanda il nome completo della stampante contenente "Samsung". Solo che non funziona. Quando il codice esegue

    .Application.ActivePrinter = sPrinter

    vien fuori il runtime error 5216, problema con la stampante. Su internet, sembra che l'unica causa sia il nome sbagliato, ma questo è improbabile (nulla è impossibile) visto che la stringa sPrinter, esaminata col debugger, contiene esattamente quello che mi aspetto e rispecchia il contenuto del registro.
    L'unico indizio che ho è che, provato sul mio PC, funziona, ma io non ho una stampante connessa ad un altro pc, ma solo due stampanti wireless oltre a quelle virtuali, per cui nessuna delle mie prove restituisce un nome con \qualcosa ome stampante. Purtroppo non riesco a trovare suggerimenti in rete, e magari il problema non è quello.
    ********
    C'è anche un secondo problema. Su uno dei tre pc (e solo lì) l'istruzione

    regobj.EnumValues HKEY_CURRENT_USER, "SoftwareMicrosoftWindows NTCurrentVersionDevices", aDevices, aTypes

    va in runtime error, libreria non registrata. Le installazioni dei tre pc sono identiche (win7, office 2010) e anche qui non ho idea di che libreria si tratti.
     
    Sub PrintRicevuta(numeroricevuta As String)
    'Mail merge e stampa su stampante di default
    
    'On Error GoTo ErrorHandler
    
    ' open template in Word
    Dim filepath, SQLStmt, WorkbookName As String
    Dim WordApp As Word.Application
    Dim WordDoc As Word.Document
    Set WordApp = New Word.Application
    filepath = ThisWorkbook.Path
    WorkbookName = ThisWorkbook.FullName
    Debug.Print filepath, numeroricevuta
    With WordApp
        .Visible = True
        Set WordDoc = .Documents.Open(filepath & "stamparicevuta.docx")
    End With
    SQLStmt = "SELECT * FROM `Ricevute$` WHERE [Numero] = " & numeroricevuta
    'MailMerge selected records from table to Word document
    With WordDoc
        With .MailMerge
            .MainDocumentType = wdFormLetters
            .OpenDataSource Name:=WorkbookName, _
            ConfirmConversions:=False, ReadOnly:=False, LinkToSource:=True, _
            AddToRecentFiles:=False, PasswordDocument:="", PasswordTemplate:="", _
            WritePasswordDocument:="", WritePasswordTemplate:="", Revert:=False, _
            Format:=wdOpenFormatAuto, Connection:="rangericevute", _
            SQLStatement:=SQLStmt, SQLStatement1:="", _
            SubType:=wdMergeSubTypeAccess
       
            .SuppressBlankLines = True
                          
            With .DataSource
                .FirstRecord = wdDefaultFirstRecord
                .LastRecord = wdDefaultLastRecord
            End With
            
       'Selezionare la stampante. Per questa funzione, la stampa
       'viene diretta sulla stampante di rete SAMSUNG ML-8610
       ' Bisogna trovare il nome completo di windows per questa stampante
       
        Dim sPrinter As String
        Dim sDefaultPrinter As String
        Debug.Print "Default printer: ", Application.ActivePrinter
        'store default printer
        sDefaultPrinter = Application.ActivePrinter
        'Get complete name using known characters
        sPrinter = GetPrinterFullName("Stylus Office")
        If sPrinter = vbNullString Then ' no match
            Debug.Print "No match"
        Else
            .Application.ActivePrinter = sPrinter
            Debug.Print "Temp printer: ", .Application.ActivePrinter
            'execute merge and Print with temp printer
            Debug.Print "Default printer: ", Application.ActivePrinter
            .Destination = wdSendToPrinter
            .Execute Pause:=False
            .Application.ActivePrinter = sDefaultPrinter ' restore default printer
        End If
        End With
               
    End With
    Application.ActivePrinter = sDefaultPrinter ' restore default printer
    WordDoc.Close SaveChanges:=False
    WordApp.Quit
    
    ErrorHandlerExit:
    Exit Sub
    ErrorHandler:
    If Err = 429 Then
    'word is not running; open word with CreateObject
    Set appWord = CreateObject(Class:="Word.Application")
    End If
    
    End Sub
    
    *************************************************
    Public Function GetPrinterFullName(Printer As String) As String
     
        ' This function returns the full name of the first printerdevice that matches Printer.
        ' Full name is like "PDFCreator on Ne01:" for a English Windows and like
        ' "PDFCreator sur Ne01:" for French.
        ' Created: Frans Bus, 2015. See 
     
        Const HKEY_CURRENT_USER = &H80000001
        Dim regobj As Object
        Dim aTypes As Variant
        Dim aDevices As Variant
        Dim vDevice As Variant
        Dim sValue As String
        Dim v As Variant
        Dim sLocaleOn As String
         
        ' get locale "on" from current activeprinter
        v = Split(Application.ActivePrinter, Space(1))
        sLocaleOn = Space(1) & CStr(v(UBound(v) - 1)) & Space(1)
         
        ' connect to WMI registry provider on current machine with current user
        Set regobj = GetObject("WINMGMTS:{impersonationLevel=impersonate}!\.
    ootdefault:StdRegProv")
         
        ' get the Devices from the registry
        regobj.EnumValues HKEY_CURRENT_USER, "SoftwareMicrosoftWindows NTCurrentVersionDevices", aDevices, aTypes
         
        ' find Printer and create full name
        For Each vDevice In aDevices
            ' get port of device
            regobj.GetStringValue HKEY_CURRENT_USER, "SoftwareMicrosoftWindows NTCurrentVersionDevices", vDevice, sValue
            ' select device
            If InStr(vDevice, Printer) > 0 Then
            'If Left(vDevice, Len(Printer)) = Printer Then ' match!
                ' create localized printername
                GetPrinterFullName = vDevice & sLocaleOn & Split(sValue, ",")(1)
                Exit Function
            End If
        Next
         
        ' at this point no match found
        GetPrinterFullName = vbNullString
     
    End Function
    
    



  • di Vecchio Frac data: 27/06/2017 21:43:03

    Io non pasticcerei col registro neanche in lettura :)
    Tempo fa e per una questione simile avevo ricavato un codice che enunciava le stampanti collegate in locale, stampanti di rete comprese; lo devo recuperare e poi te lo posto. Se riesco prima, anche stasera, altrimenti domani in ufficio con la testa libera da gatti e bambini :)

    Questa frase iniziale però mi stuzzica: "...è destinata ad essere usata da almeno tre utenti diversi. Non simultaneamente, ovviamente, altrimenti la numerazione consecutiva avrebbe dei problemi".
    Potresti utilizzare un file di appoggio in cartella cui tutti i pc possono accedere in lettura e scrittura, come sistema di prenotazione del numero: il pc che sta per stampare una ricevuta si "prenota" il primo numero libero e lo scrive nel file, il pc successivo legge a che numero siamo arrivati e si prenota il numero successivo, ecc. Un semplice file di testo va benone magari coniato come un file INI con le sue sezioni e le chiavi (da poco mi son (ri)fatto una mia routine INImanager che uso con soddisfazione). Però questa è solo un'idea eh... ci sono anche lì delle complicazioni (soprattutto quando non va la rete ^_^)






  • di Vecchio Frac data: 27/06/2017 21:48:50

    Fortunatamente ho ripescato il vecchio articolo che avevo scritto :)
    Ecco uno stralcio della descrizione:
    "(...)La procedura presenta in una inputbox l'elenco delle stampanti installate nel sistema (non elenca quella già predefinita), numerate da 1 in poi; si indica il numero della stampante desiderata e si conferma. Viene quindi stampato il foglio attivo(...)"
    Nel codice ho commentato la riga che stampa il foglio (evitiamo gli sprechi ^_^) così puoi prendere le mosse per studiarti le sub coinvolte (partendo da Main ovviamente).


     
    'in un modulo
    Option Explicit
    
    'seleziona una stampante e la rende temporaneamente predefinita
    
    Type PRINTER_INFO_1
        flags As Long
        pPDescription As Long
        pName As Long
        pComment As Long
    End Type
    
    Type PRINTER_INFO_5
        pPrinterName As Long
        pPortName As Long
        Attributes As Long
        DeviceNotSelectedTimeout As Long
        TransmissionRetryTimeout As Long
    End Type
    
    Private Const PRINTER_ENUM_LOCAL = &H2
    Private Declare Function Enumprinters Lib "winspool.drv" Alias "EnumPrintersA"_
    (ByVal flags As Long, ByVal Name As String, ByVal Level As Long, pPrinterEnum _
    As Any, ByVal cdBuf As Long, pcbNeeded As Long, pcReturned As Long) As Long
    Private Declare Sub MoveMemory Lib "KERNEL32" Alias "RtlMoveMemory" (Dest As _
    Any, Source As Any, ByVal length&)
    Private Declare Function SetDefaultPrinter Lib "winspool.drv" Alias _
    "SetDefaultPrinterA" (ByVal pszPrinter As String) As Long
    
    
    Sub Main()
    Dim original_printer As String, new_printer As String, i As Integer
    
        original_printer = ActivePrinter
        new_printer = EnumPrinter
        If new_printer = "" Then Exit Sub
        SetDefaultPrinter new_printer
        'ActiveSheet.PrintOut     '<<<<<< decommentare per stampare il foglio attivo
        
        i = InStrRev(original_printer, " ", InStrRev(original_printer, " ")) - 1
        SetDefaultPrinter Left(original_printer, i)
    End Sub
    
    
    Private Function EnumPrinter()
        Dim rc As Long, i As Integer, nSizeOfStruct As Long, Level As Long
        Dim pPrinterEnum() As Byte, pcbNeeded As Long, pcReturned As Long
        Dim PI_1() As PRINTER_INFO_1
        Dim Msg As String
        Dim v As Variant
        Dim ChgP As Integer
        
        Level = 1
        rc = Enumprinters(PRINTER_ENUM_LOCAL, vbNullString, Level, ByVal 0&, 0, pcbNeeded, pcReturned)
        ReDim pPrinterEnum(pcbNeeded - 1) As Byte
        rc = Enumprinters(PRINTER_ENUM_LOCAL, vbNullString, Level, _
    pPrinterEnum(0), pcbNeeded, pcbNeeded, pcReturned)
        ReDim PI_1(pcReturned - 1) As PRINTER_INFO_1
        nSizeOfStruct = Len(PI_1(0))
        Msg = "Digita il numero della nuova stampante predefinita:" & vbCrLf
        For i = 0 To pcReturned - 1
            Call MoveMemory(PI_1(i), pPrinterEnum(nSizeOfStruct * i), _
    nSizeOfStruct)
            Msg = Msg & i + 1 & " > " & gGetStr(PI_1(i).pName, 64) & vbCrLf
        Next i
        v = InputBox(Msg)
        If Trim(v) = "" Then EnumPrinter = "": Exit Function
        ChgP = v
        EnumPrinter = gGetStr(PI_1(ChgP - 1).pName, 64)
    End Function
    
    Private Function gGetStr(pString As Long, nBytes As Long) As String
        ReDim BufArray(nBytes) As Byte
        Call MoveMemory(BufArray(0), ByVal pString, nBytes)
        gGetStr = gGetStrBuffer(StrConv(BufArray(), vbUnicode))
    End Function
    
    Private Function gGetStrBuffer(sString As String) As String
        If InStr(sString, vbNullChar) Then
            gGetStrBuffer = Left$(sString, InStr(sString, vbNullChar) - 1)
        Else
        gGetStrBuffer = sString
        End If
    End Function






  • di roberto21 data: 28/06/2017 17:41:05

    Un problema pare risolto. Nonostante tutto quanto letto e (pensavo) capito, l'unico modo per eliminare il run time error 5216 pare sia eliminare la parte finale della stringa del nome della stampante come ricevuta dalla funzione. Cioè, la funzione mi restituisce "\UTENTE1-PCSamsung ML-1860 Series su Ne09", devo eliminare " su Ne09" e tutto funziona. Va a capire...



  • di Vecchio Frac data: 28/06/2017 17:56:25

    Hai testato la mia procedura per vedere se ti dà gli stessi problemi?





  • di roberto21 data: 28/06/2017 20:17:56

    Non ancora, la sto studiando, sto cercando di capirla prima di usarla...Grazie comunque, ti tengo informato.



  • di Vecchio Frac data: 28/06/2017 22:29:11

    Se ho un minuto domattina te lo dettaglio io (se nel frattempo non hai raggiunto una qualche conclusione).





  • di Vecchio Frac data: 29/06/2017 08:38:08

    'PRINTER_INFO_1 e _5 definiscono la struttura API Windows standard per le informazioni sulla stampante
    Type PRINTER_INFO_1
    End Type

    Type PRINTER_INFO_5
    End Type

    'le API che recuperano l'elenco delle stampanti installate e che impostano la stampante predefinita
    Private Declare Function Enumprinters Lib "winspool.drv" Alias "EnumPrintersA"
    Private Declare Function SetDefaultPrinter Lib "winspool.drv"

    'procedura principale che imposta una stampante scelta dall'utente. La stampante è utilizzata solo per il lavoro corrente, poi viene ripristinata la stampante precedente
    Sub Main()
    End Sub

    'la funzione che si occupa di recuperare dal sistema l'elenco delle stampanti installate, senza " su Ne00" (che non è influente per gli scopi della procedura)
    Private Function EnumPrinter()
    End Function

    'funzione di servizio che restituisce il nome di ogni stampante di sistema installata. Richiamata da EnumPrinter con un ciclo For i "gGetStr(PI_1(i).pName, 64)"
    Private Function gGetStr(pString As Long, nBytes As Long) As String
    End Function

    'funzione di servizio che riempie il buffer stringa col nome della stampante. Richiamata da gGetStr()
    Private Function gGetStrBuffer(sString As String) As String
    End Function





  • di roberto21 data: 29/06/2017 11:01:30

    La funzione EnumPrinters vien chiamata con PRINTER_ENUM_LOCAL = &H2, ma questo non dà solo le stampanti locali? Nel mio caso, mi serve una stampante di rete, dovrei usare qualche altro flag (&H40)?

    Tanto per info, mi pare di aver capito che il fatto che non serva "su Ne0x" (come scrivevo nell'ultimo post) sia dovuto al fatto che sto andando in stampa con WORD, non con Excel. Se avessi stampato con excel, PARE (e sottolineo pare) che la clause su NExx sia necessaria. Mi sembra una cosa un po' cervellotica.



  • di Vecchio Frac data: 29/06/2017 11:27:47

    cit. " non dà solo le stampanti locali?"
    ---> A me funziona con tutte le stampanti installate sul pc, di rete e locali. Ho appena fatto una prova. Funziona ugualmente in Excel e in Word (Win Seven, Office 2007).





  • di roberto21 data: 29/06/2017 14:01:07

    OK, prima prova fatta (sul mio pc) e funziona. Appena posso, vado a provare il tutto sugli altri pc. Dovrei modificare il codice in modo da non avere il msgbox, nel senso che la stampante deve diventare la Samsung, senza farla scegliere agli utenti. Probabilmente devo passare la stringa "samsung" a EnumPrinter, individuare il nome completo e usare quello come default temporaneo. Appena posso, ci provo. Grazie



  • di Vecchio Frac data: 29/06/2017 15:33:08

    Devi modificare EnumPrinter infatti.
    Come lo faccia, sei libero di scegliere (potresti anche farla diventare una funzione con parametro, idea interessante).
    L'ho modificata come segue per test e dovrebbe funzionare (brutta idea codificare "samsung" nel codice ma ripeto potresti parametrizzarla).
     
    Private Function EnumPrinter()
        Dim rc As Long, i As Integer, nSizeOfStruct As Long, Level As Long
        Dim pPrinterEnum() As Byte, pcbNeeded As Long, pcReturned As Long
        Dim PI_1() As PRINTER_INFO_1
        Dim Msg As String
        Dim v As Variant
        Dim ChgP As Integer
        
        Level = 1
        rc = Enumprinters(PRINTER_ENUM_LOCAL, vbNullString, Level, ByVal 0&, 0, pcbNeeded, pcReturned)
        ReDim pPrinterEnum(pcbNeeded - 1) As Byte
        rc = Enumprinters(PRINTER_ENUM_LOCAL, vbNullString, Level, pPrinterEnum(0), pcbNeeded, pcbNeeded, pcReturned)
        ReDim PI_1(pcReturned - 1) As PRINTER_INFO_1
        nSizeOfStruct = Len(PI_1(0))
        Msg = "Digita il numero della nuova stampante predefinita:" & vbCrLf
        For i = 0 To pcReturned - 1
            Call MoveMemory(PI_1(i), pPrinterEnum(nSizeOfStruct * i), nSizeOfStruct)
            v = gGetStr(PI_1(i).pName, 64)
            If InStr(LCase(v), "samsung") > 0 Then Exit For
        Next i
        If EnumPrinter = "" Then Exit Function
        EnumPrinter = gGetStr(PI_1(i).pName, 64)
    End Function
    






  • di roberto21 data: 30/06/2017 19:26:36

    Le prove sugli altri pc sono state purtroppo inconclusive. La stampante collegata ad un altro pc (il server) non viene vista nell'enumerazione, quindi per provare ho dovuto collegarla direttamente alla presa usb del router. In questo modo compare nell'elenco, ma rifiuta testardamente di stampare alcunchè (stampa solo messaggi di errore con strani codici). Questo deve essere un altro problema, legato probabilmente al tipo di connessione. Comunque, usando la tua tecnica di enumerazione, almeno è sparito l'errore runtime di "libreria non catalogata" che veniva fuori prima, nella RegObj.EnumValues. Non ho avuto molto tempo, ci riproverò. E' spiegabile perchè la stampante connessa ad un altro PC (e regolarmente Shared) non viene elencata nell'ennmerazione?



  • di Vecchio Frac data: 30/06/2017 20:57:06

    Ma la stampante collegata al server (e condivisa) viene vista come stampante di rete dagli altri pc? Se per esempio apri Word riesci a stamparci? Se l'hai collegata al router e viene vista forse non è un problema di condivisione di rete ma di come gli altri pc accedono alle stampanti condivise. I sistemi operativi son gli stessi immagino. Bisogna googlare un po' e cercare info sulle stampanti condivise (server di stampa, abilitazioni, ecc.). Sulle stampe: strane potrebbero anche esserci problemi di driver non installati o non corretti sui pc della rete.





  • di roberto21 data: 30/06/2017 22:02:28

    La stampante connessa al server viene vista regolarmente dai pc come stampante di rete ed è regolarmente utilizzabile. A parte il pc che fa le bizze con l'errore di runtime, con gli altri ieri ero riuscito a stampare andando a cercare ul nome dalla chiave di registro. Il fatto che non funzioni del tutto attaccata al router penso c'entri poco con il nostro caso (anche stampando un file qualsiasi non funziona).
    I driver sui pc ci sono tutti, come dicevo la stampante funziona regolarmente da tutti i pc quando è connesa al server.
    Ci riprovo. Appena posso, riattacco la stampante al server e continuo a provare.
    Ciao



  • di roberto21 data: 04/07/2017 13:25:26

    La famigerata stampante è stata scollegata dal router e rimessa sul server, ed ha ripreso a stampare senza problemi. In questo momento non ho tempo o voglia di pensare al perchè non funzioni sul router, magari ci torno dopo.
    Ho fatto la prima prova modificando la EnumPrinterin modo da renderla parametrica (gli passo la stringa "Samsung"). Ovviamente, non funziona perchè la stampante attaccata al server non viene elencata, quindi la Instr è sempre zero (verificata col debugger). Ho provato a chiamare la EnumPrinters con PRINTER_ENUM_LOCAL OR PRINTER_ENUM_CONNECTIONS, ma non cambia niente. Non so se provare con OR e qualche altra flag, tanto non costa niente. Ho scoperto parò per caso che se la stampante e cichiarata come stampante di default viene elencata dalla EnumPrinters, mentre se la stampante di defaultè un'altra (come normalmente è), sparisce dall'elenco.
    Continuo a provare.


    PS
    Non è vero che non costa niente. Ho provato con

    Level = 1
    rc = Enumprinters(PRINTER_ENUM_LOCAL | PRINTER_ENUM_NETWORK, vbNullString, Level, ByVal 0&, 0, pcbNeeded, pcReturned)
    ReDim pPrinterEnum(pcbNeeded - 1) As Byte

    e ho ottenuto rc= 0 (EnumPrinters fallita o non ha trovato stampanti, ma essendo un OR almeno quelle di prima doveva trovarle... o no?) e di conseguenza Redim è andato in runtime error 9 (pcbNeeded è zero, quindi indice out of range). Non ho capito perchè l'OR di quei due flag non funziona.



  • di Vecchio Frac data: 04/07/2017 20:57:41

    cit. "Non ho capito perchè l'OR di quei due flag non funziona."
    ---> LOL, ma tu da che linguaggio di programmazione provieni?
    In VBA l'operatore OR è OR e basta, non è sostituito dalla pipe | ...

    Level = 1 
    
    rc = Enumprinters(PRINTER_ENUM_LOCAL OR PRINTER_ENUM_NETWORK, vbNullString, Level, ByVal 0&, 0, pcbNeeded, pcReturned)
    ReDim pPrinterEnum(pcbNeeded - 1) As Byte






  • di roberto21 data: 05/07/2017 13:03:20

    Vero. Ho avuto un momento di oscuramento (credo che in C++ logical or sia ||). MA queste due prove, la seconda senz'altro superflua

    rc = Enumprinters(PRINTER_ENUM_LOCAL OR PRINTER_ENUM_NETWORK, vbNullString, Level, ByVal 0&, 0, pcbNeeded, pcReturned)

    ReDim pPrinterEnum(pcbNeeded - 1) As Byte



    rc = Enumprinters((PRINTER_ENUM_LOCAL OR PRINTER_ENUM_NETWORK), vbNullString, Level, ByVal 0&, 0, pcbNeeded, pcReturned)

    ReDim pPrinterEnum(pcbNeeded - 1) As Byte



    mi hanno dato lo stesso risultato (rc=0, pcbNeeded = 0).

    Ho risolto (?) il problema chiamando enumPrinters col flag &H6. Con questo flag, vengono enumerate tutte le stampanti locali, e anche quella di rete sul server.