Gianni Giaccaglini

Tricks & mini applics on WPF
posts - 46, comments - 0, trackbacks - 0

Circolare Outlook con indirizzi in archivio CSV

Circolare Outlook con indirizzi in archivio CSV

 

ULTIM’ORA.  In precedenti interventi su questo blog ho ipotizzato, errando, che il salvataggio (o la spedizione?) di messaggi Outlook tramite (OLE) Automation funzionasse solo a patto che Outlook fosse aperto. In realtà, la possibilità di farlo lavorare, diciamo così, in background esiste. Me lo ha suggerito il buon Diego Cattaruzza che qui ringrazio, anche se suggerendo solo in termini generali il metodo Logon previo ricorso a un opportuno Namespace... Dopo diverso tempo riscartabellando un libraccio su Outlook ho trovato la ricetta, che anticipo subito:

  Dim Ns As Outlook.NameSpace = Outlk.GetNamespace("MAPI")

 Ns.Logon("Outlook")

Essa andrà applicata, da chi ci tenesse, ai precedenti post dedicati all’invio di circolari Outlook. Di conseguenza, la precedente routine legata al clic sul pulsante Button5 (etichettato “Spedisci”, v. più avanti) oltre che emendata in conseguenza va totalmente deprivata di questo brano:

Dim Avviso = "Microsoft Outlook è attivato?" & vbLf & "(Altrimenti il codice NON fa nulla"

    If MessageBox.Show(Avviso, "Controlla!", MessageBoxButton.YesNo) =  MessageBoxResult.No Then

      MessageBox.Show("Provvedi!", "!!!", MessageBoxButton.OK, MessageBoxImage.Exclamation)

      Exit Sub

    End If

Questo articolo è l’ultimo atto della saga in cui ho rivisitato la tecnologia (OLE) Automation applicata a Excel e Outlook. Pertanto non starò a ripetermi né a fare il sunto dei precedenti, invito solo a rileggersi il precedente post, ove è affidato appunto ad Automation l’accesso a un IndirizziOutlook.xls, contenente una tabella di 4 campi che si presenta così:

Nome

Cognome

Indirizzopostaelettronica

Nomevisualizzatopostaelettronica

Armando

Rossetti

armross@megasoft.com

omissis

Paola

Bertoldi

paolab@megasoft.com

omissis

ecc.

ecc

ecc.

omissis

 

Microsoft Outlook permette di esportarla sia in formato Excel sia in un file IndirizziOutlook.csv di tipo comma separated value. Fin dall’inizio mi sono affannato a indagare come caricare quest’ultimo, ottenendo solo indicazioni generiche e comunque poco adatte al limitato comprendonio di un amatore qual sono.

Finalmente uno spunto più chiaro mi è pervenuto da Stefano Pranzo, un valido membro di Wpfitalia. Il carteggio si trova sul Forum. Di fatto, rinunciando a una soluzione più completa (con Classe canonica e Binding XAML) a torto o ragione mi sono accontentato di questo snippet, che mi preme riportare subito:

Dim TutteRighe() = _

    File.ReadAllLines(My.Computer.FileSystem.CurrentDirectory & "\IndirizziOutlook.csv")

 

Aggiungendo che l’istruzione contiene pure una chicca, scoperta autonomamente (giuro). La giro ai miei pari grado mentre i più scafati faranno spallucce (lo sapevamo già!). Sia come sia, il trucchetto ovviamente affidato a una preliminare Imports System.IO, sostituisce un normale argomento con path completo tipo “C:\...\IndirizziOutlook.csv” la directory corrente e per funzionare occorre preliminarmente copiare il nostro csv nelle directory bin\Debug e bin\Release del progetto.

Completo il quadro riportando tutto il codice relativo al primo pulsante di questa nuova soluzione WPF, preceduto dagli incipit del caso:

Imports Microsoft.Office.Interop ' Occorre solo per MS Outlook

Imports System.IO

Imports System.Threading ' Per il messaggio temporaneo (v. più avanti)

 

Class MainWindow

    Dim Outlk As Microsoft.Office.Interop.Outlook.Application

    Dim IndirEmail() As String

    Dim IndirEmailScelti(0) As String

 

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button1.Click

    ' Caricamento archivio IndirizziOutlook.csv

    Static swFileCSVCaricato As Boolean

    If swFileCSVCaricato Then

    If MessageBox.Show("Dati e-mail già presenti... Ripeto il caricamento?", "Attenzione!", MessageBoxButton.YesNo) = _

      MessageBoxResult.No Then

    Exit Sub

    End If

    End If

    Dim TutteRighe() = _

    File.ReadAllLines(My.Computer.FileSystem.CurrentDirectory & "\IndirizziOutlook.csv")

    Dim NumRighe = TutteRighe.Length

    Dim TutteCol(3) As String

    Dim Riga As String

    Dim NomiCogn(NumRighe - 1) As String

    ReDim IndirEmail(NumRighe - 1)

    Dim k = -1 ' Per saltare le intestazioni

    For Each Riga In TutteRighe

    TutteCol = Riga.Split(";")

    If k >= 0 Then

    NomiCogn(k) = TutteCol(0) & " " & TutteCol(1)

    IndirEmail(k) = TutteCol(2)

    End If

    k += 1

    Next

    Array.Sort(NomiCogn, IndirEmail)  ' Ordina nomi + cognomi ed email corrisp.ti!

    ListBox1.ItemsSource = NomiCogn

    ListBox1.SelectedIndex = 0 ' Seleziona il primo record

    swFileCSVCaricato = True

    End Sub

 

Anticipo che lo scopo della routine è duplice: il popolamento di una ListBox1 della finestra WPF (o una Form, volendo) con tutti i vari “Mario Rossi”, “Peppina Mastropasqua” ecc. e, al tempo stesso, dei corrispettivi indirizzi email nel vettore comunitario IndirEmail. Questo perché solo il primo va mostrato all’utente.

La parte iniziale la affido all’esegesi autogestita del visitatore di questo blog, per il resto mi limito a dire che il loop For Each Riga In TutteRighe. . . Next spazzola le righe del .csv incluse intestazioni (che vanno skippate) e ad ogni passo pone nel vettore TutteCol di 4 elementi la Riga.Split(“;”) provvidenziale codice che separa i campi sulla base del delimitatore “;”.

Nota Il separatore punto e virgola si ha nell’edizione italiana. In quella inglese c’è la virgola. Ne può nascere, ahimè, un guaio per gli anglofoni/fili cui si rimedia ma, temo, solo a livello debug.

Cos’altro dire? Solo che l’istruzione NomiCogn(k) = TutteCol(0) & " " & TutteCol(1) concatena nomi e cognomi ottenuti con l’indice 0 e, rispettivamente, 1 di TutteCol, dopo di che tale vettore viene affibbiato alla proprietà ItemsSource della ListBox, inoltre merita di essere evidenziata la precedente Array.Sort(NomiCogn, IndirEmail) la quale riordina in modo coerente entrambi i vettori (guai a farlo separatamente!).

Il confronto che convince. Rispetto a precedenti soluzioni con OLE Automation applicato a IndirizziOutlook.xls a colpo d’occhio si osserva una velocità operativa molto maggiore, oltre che un minor spreco di RAM.

Uno sguardo al layout

Per brevità non riporto l’XAML ma mi limito a dare un’idea dell’aspetto risultante come segue:


 

Nominativi completi

       

Nominativi scelti

 

 

 

===>

 

 

     
     
 

Azzera
scelti

 
     
     

CIRCOLARE

               

 

                 
         

 

Tutti

   

Carica
nomi e ind.

         

Prepara
circolare

 

Spedisci

 

In due parole, i controlli essenziali in alto sono una ListBox1 e una ListBox2 il cui destino è indicato dalle label “Nominativi completi” e “Nominativi scelti”, fra le quali sono interposti i button etichettati “===>” e “Azzera scelti”, al centro c’è una grossa TextBox1 per ospitare la circolare, infine in basso si hanno tre pulsanti dallo scopo chiaramente indicato da ciascun Content più una casella CheckBox1, che discrimina se la circolare va inviata a tutto il … stavo per dire cucuzzaro, no!, all’intero indirizzario oppure ai soli soggetti scelti.

Nota. Si ricorda che una proprietà come Content="Carica 
Nomi e indirizzi" grazie a una costante XML &#valore; inserisce un a-capo (linefeed o CR, con 13 anziché 10) o altro carattere speciale.

 

Ognuno può interpretare a suo gusto l’XAML sopra mostrato, magari migliorandolo anche esteticamente.

Altre codifiche VB

Si riveda la Sub dell’evento Click del pulsante etichettato “Carica nomi e ind.”, quindi passiamo alle altre routine.

La prima è abbinata al pulsante etichettato con la rozza freccia ===> (migliorabile!).

Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button3.Click

' Trasferimento indirizzi scelti

    Dim i = ListBox1.SelectedIndex

    If i = -1 Then

    MessaggioTemporaneo("Devi selezionare un record!", 2000)

    ' MessageBox.Show("Devi selezionare un record!", "Attenzione!", MessageBoxButton.OK, MessageBoxImage.Error) ' SOLUZIONE CLASSICA

    Exit Sub

    End If

    Dim NomeScelto As String = ListBox1.Items(i)

    If ListBox2.Items.Count = 0 Then

    ListBox2.Items.Add(NomeScelto)

    IndirEmailScelti(0) = IndirEmail(i)

    Exit Sub

    End If

    For Each Elem In ListBox2.Items

    If Elem = NomeScelto Then

    MessaggioTemporaneo(NomeScelto & " è già presente", 2000)

    ' MessageBox.Show(NomeScelto & " esiste già!")

    Exit Sub

    End If

    Next

    Dim k = IndirEmailScelti.Length

    ListBox2.Items.Add(NomeScelto)

    ReDim Preserve IndirEmailScelti(k)

    IndirEmailScelti(k) = IndirEmail(i)

    ' MsgBox(ListBox2.Items(k) & vbLf & IndirEmailScelti(k))

    End Sub

 

Saltando per ora le prime righe che lanciano un chiaro avviso temporaneo, qualora nessun item della prima casella a discesa fosse selezionata (Listbox1.SelectedIndex = -1, cosa peraltro molto improbabile, se si riflette) viene dapprima messo in NomeScelto l’item i della ListBox1. Qui va precisato che nelle Dichiarazioni è stata posta una Dim IndirEmailScelti(0) che la rende non vuota, per cui la prima volta che si ha IndirEmailScelti(0) non si hanno proteste né errori. Dopo questa pignola, ma indispensabile, segnalazione il commento del resto è affidato a chi legge che si dovrebbe convincersi che viene garantito l’aggiornamento parallelo della ListBox2, a furia di Items.Add¸ dei corrispondenti indirizzi email, a furia di complicati Redim Preserve  che probabilmente si possono rendere più “puliti”.

Quest’altra Sub parla da sola:

Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button4.Click

    ' Pulisci la lista dei nominativi scelti e la lettera

    ListBox2.Items.Clear()

    TextBox1.Clear()

    ReDim IndirEmailScelti(0)

End Sub

 

Va solo detto che ho preferito questo drastico repulisti in caso di utente nel pallone, anziché più articolate (e noiose) azioni di ripristino

Andiamo poi a vedere come viene preparata la circolare.

Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button2.Click

    ' Preparazione del testo della circolare

    Dim Nominativi = ""

    Dim i = 0

    If CheckBox1.IsChecked Then

    For Each Elem In ListBox1.Items

    Nominativi &= Elem & vbCr

    i += 1

    Next

    Else

    For Each Elem In ListBox2.Items

    Nominativi &= Elem & vbCr

    i += 1

    Next

    End If

    If Nominativi = "" Then Exit Sub

    TextBox1.Text = "Gentili signori" & vbCr & Nominativi & vbCr & "Distinti saluti"

End Sub

 

Palesemente il testo prevede un’inizio “Gentili signori” seguito dalla serie di

Pasuqlino Maragià

Marianna Buttalapasta

Ernesto Diociguardi

. . .

Con la chiusa “Distinti saluti”. L’importante è rimarcare che questo banale scritto può sempre essere corretto dall’utente, sia nell’applicazione sia in Outlook,come stiamo per vedere.

Eccoci al pulsante che spedisce, anzi che prepara le bozze Outlook della circolare.  Come anticipato in apertura, rispetto alla precedente versione è stata utilizzato il Logon tramite il Namespace “MAPI” (istruzioni evidenziate in neretto), eliminando in conseguenza l’inutile richiesta all’utente di aprire Outlook.

Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.Windows.RoutedEventArgs) Handles Button5.Click

    ' Spedisci circolari (anzi salvale come bozze)

    If TextBox1.Text = "" Then

    ' Messaggio che compare nella lblMESS, invisibile per default

       MessaggioTemporaneo("Lettera vuota! Attenzione", 2000)

    Exit Sub

    End If

    Dim Outlk As New Outlook.Application

    Dim Ns As Outlook.NameSpace = Outlk.GetNamespace("MAPI")

    Ns.Logon("Outlook")

 

    Dim MioMess As Outlook.MailItem

    MioMess = Outlk.CreateItem(Outlook.OlItemType.olMailItem)

    Dim Indirizzi = ""

    Dim i = 0

    If CheckBox1.IsChecked Then

    For Each Elem In ListBox1.Items

    Indirizzi &= IndirEmail(i) & ";"

    i += 1

    Next

    Else

    For Each Elem In ListBox2.Items

    Indirizzi &= IndirEmailScelti(i) & ";"

    i += 1

    Next

    End If

    With MioMess

    .Subject = "Varie"

    .To = Indirizzi

    .Body = TextBox1.Text

    .Save()

    End With

    NS.Logoff()

    MioMess = Nothing

    Outlk = Nothing

    TextBox1.Clear()

End Sub

A questo punto me la sbrigo alla grande, limitandomi in primo luogo a invitare alla rilettura dei precedenti articoli in materia, per l’uso della tecnologia Automation per Outlook.

Nota. Che personalmente ritengo inevitabile, salvo segrete librerie .NET a me del tutto ignote…

Ultima chicca,  messaggio a tempo

Taccio poi del tutto sul dialogo con l’utente per ricordargli che – anche qui salvo segreti ignoti a chi scrive, che è incappato in drammi con Logon e Logoff… - MS Outlook dev’essere aperto (sennò nemmeno scaturiscono errori a run-time!). E svelo infine che nella finestra XAML ho collocato pure una label lblMESS  di questo tipo:

<Label Grid.Row="1" Margin="22,0,23,40" Name="lblMESS" Visibility="Hidden" Foreground="DarkBlue" FontSize="14" Height="29" VerticalAlignment="Bottom">

               MESSAGGIO DEFAULT...

</Label>

 

La sua proprietà Visibility=”Hidden” la rende invisibile a run-time, mentre a tempo di progetto si vede chiaramente. Con essa ho sperimentato una routine con argomenti abbastanza sofisticata:

Private Sub MessaggioTemporaneo(ByVal Msg As String, ByVal tempo As Double)

    Dim myThread As New Thread(

      Sub()

      Dispatcher.Invoke(Sub()

            With lblMESS

            .Content = Msg

            .Visibility = Windows.Visibility.Visible

            End With

        End Sub)

      Thread.Sleep(tempo)

      Dispatcher.Invoke(Sub()

            lblMESS.Visibility = Windows.Visibility.Hidden

        End Sub)

      End Sub)

    ' Esecuzione di myThread

    myThread.Start()

End Sub

 

Tale routine è invocata dall’istruzione seguente:

If TextBox1.Text = "" Then

  ' Messaggio che compare nella lblMESS, invisibile per default

   MessaggioTemporaneo("Lettera vuota! Attenzione", 2000)

   Exit Sub

End If

 

Che, qualora la TextBox1 sia vuota segnala l’anomalia facendo comparire il messaggio anziché con la solita MessageBox nella label predetta.

Affido questa ricetta ai più esperti, assicurando gli altri che funziona a patto di non dimenticare la direttiva Imports System.Threading.

Print | posted on venerdì 30 marzo 2012 22:11 |

Feedback

No comments posted yet.

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 3 and 8 and type the answer here:

Powered by:
Powered By Subtext Powered By ASP.NET