Jeg har gennem det sidste stykke tid haft et skuffeprojekt at ligge som jeg har kodet lidt på, når tiden har været til det. I gamle dage, i Office 2003 tiden, havde Ole Kjeldsen lavet et værktøj til at booke mødelokaler her i Hellerup. Værktøjet var ikke kompatibelt med Office 2007. Jeg samlede jeg bolden op, og har implementeret det på ny, med nye teknologier (det var lavet i VBA). Jeg har nu en produktionsklar (sagt af en udvikler) version. Jeg vil tro at jeg har brugt knap 1 uge på selve funktionaliteten omkring mødebooking. Men så har jeg tilgengæld brugt godt en uge på WPF, templates, databinding og threading i VSTO.
Det har resulteret i en Outlook 2007 Add-in.
Applikationen viser to tabs, en for hver etage. På hver tab er der et billede af grundplanen. På grundplanen er alle mødelokalerne placeret, og er illusteret med de grønne, røde, gule og grå cirkler. Der kan trykkes på de grønne mødelokaler. Ved et tryk tilføjes mødelokalet til den åbne Appointment, og du kan nu med almindelig outlook funktionalitet sende mødeindkaldelsen ud og dermed få booket mødetlokalet. I højre side vises blogs fra afdelingen, samt status for det valgte mødelokale.
Umiddelbart var der flere ting som jeg gerne ville opnå med dette add-in:
1) Selvfølgelig at det skal være nemt at finde frie mødelokaler. Vi har 42 i Hellerup, og det tager minimum 15 sekunder at åbne et mødelokale i outlook gennem "File->Open->Other User's Folder". Ofte skal man igennem temmelig mange mødelokalekalendere før man finder et ledigt mødelokale. Træls og langsommeligt.
2) Lave lidt intern "markedsføring" af afdelingen, Developer Platform & Evangelism. Hvis du vil booke et møde med toolet, så bliver du eksponeret for vores blogs. Det skulle gerne skabe noget intern synlighed på hvad sådan nogle evangelister også render rundt og laver :-)
3) og sidst men ikke mindst. Jeg trængte til at lave noget andet end Hello World ;-)
VSTO 3.0 Reelt set, så er det ikke ret meget VSTO der i denne add-in. Jeg har tilføjet en "Outlook Form Region", og gennem Wizard'en bedt om en "Separate" form. På den vis, får jeg en knap i menuen, som åbner min form. I samme Wizard har jeg valgt at min Form Region kun skal vises ved IPM.Appointment. Så er formen egentlig bundet op og jeg kan begynde lave min Winform.
Jeg startede ud med at forsøge mig med WinForms. Efter at have brugt lidt tid på at finde ud af at lave "Image Maps" ovenpå en PictureBox for at få mappet mødelokaleknapperne ind, besluttede jeg at lave en WPF usercontrol i stedet for. På min Outlook Form har jeg så brugt en ElementHost som jeg har docket min WPF usercontrol i.
WPF Her må jeg blankt erkende at jeg startede på Hello World stadiet. Jeg erkendte hurtigt mine manglende kvalifikationer. Jeg tog derfor en dag ud af kalenderen, kørte til Fakse, og besøgte Imasoft. Her brugte vi vel 4 timer effektivt på at få sat projektet op. Super hvad man får ud af at sidde sammen med eksperter :-) Vi lavede skabelonerne til 3 usercontrols:
MeetingRoomsTab: Denne kontrol er faktisk kun indholdet af en tab, er et grundplansbilledet plus nogle MSMeetingRoomUI kontroller. Bag ved billedet ligger der et Canvas, som kun har til formål at sørge for at flytte mødelokalerne relativt i forhold til størrelsen af grundplansbilledet: <Canvasx:Name="InteractiveCanvas" MouseDown="InteractiveCanvas_MouseDown" SizeChanged="InteractiveCanvas_SizeChanged" Width="{BindingElementName=InteractiveImage,Path=ActualWidth}" Height="{BindingElementName=InteractiveImage,Path=ActualHeight}" VerticalAlignment="Center" HorizontalAlignment="Center" Background="Transparent" />
Kudos til Imasoft som viste mig hvordan man kunne binde elegant i XAML.I codebehinden ser eventhandleren ud som følgende:
void InteractiveCanvas_SizeChanged(object sender, SizeChangedEventArgs e) { foreach (MeetingRoomUI room in Rooms) { Canvas.SetLeft(room, room.Position.X * InteractiveCanvas.ActualWidth); Canvas.SetTop(room, room.Position.Y * InteractiveCanvas.ActualHeight); } }
På denne vis er mødelokalerne altid placeret det samme sted oven på grundplansbilledet.
MSMeetingRoomUI: Reelt set er dette bare en Button som er stylet til bare at være en cirkel. Kontrollen har to public events:
public delegate void MeetRoomSelectedDelegate(string strRoomName); public event MeetRoomSelectedDelegate MeetRoomSelected; public delegate void MeetRoomHoverDelegate(DTO.MeetingRoom dto); public event MeetRoomHoverDelegate MeetRoomHover;
Formålet er at give besked om når et mødelokale er valgt, samt når man holder musen hen over et mødelokale. MeetingRoom objektet er bare en POCO som indholder data om et mødelokale, herunder om det er optaget eller ej.
MSMeetingRooms: Denne usercontrol den øverste container/usercontrol. Den indeholder en en TabControl, en ProgressBar nederst, og en række customcontrols til venstre, herunder en RSS Streamer (en viewer af RSS feeds). Når jeg fra min Outlook Form instantierer min MSMeetingRooms kontrol (uc nedenunder), så kan jeg dynamisk tilføje mine mødelokaler, som jeg henter fra mit Data Access layer (dal), til Tab kontrollen på MSMeetingRooms.
dal = new DAL.MeetingRoomDAL(); uc.AddMeetingRooms(0, dal.meetingRoomList. Where(r => r.Floor == 2).Select(r => r).ToList<DTO.MeetingRoom>()); uc.AddMeetingRooms(1, dal.meetingRoomList. Where(r => r.Floor == 4).Select(r => r).ToList<DTO.MeetingRoom>());
På dal objektet ligger der en List<MeetingRoom> (meetingRoomList) hvor MeetingRoom er POCO'en. Listen instantieres ved at læse data fra en XML fil:
<Rooms> <RoomRoomName="Conf Room MSDAS Venus (eks, 8 pers.)" Email="XXXXX@microsoft.com" Floor="2" Name="Venus"> <RelativeCoordinatex="0.17063" y="0.4525561" /> <Location>HELLERUP/217</Location> </Room> ....
Jeg lavede en lille helper til at tage de kendte informationer ud af AD'et og skrive dem til XML'en. Jeg brugte DirectorySearcher til at søge i AD, og LINQ to XML for at få det skrevet til en XML fil. Efterfølgende skulle jeg så bare indsætte RelativeCoordinate. Til at finde de relativekoordinater lavede jeg en primitiv Click event på mit grundplansbillede der kunne fortælle mig de relative koordinater på billedet når jeg trykkede på det. Dem kunne jeg så efterfølgende sætte ind i XML filen.
Data Access layer, Exchange webservices Efter først at være inde over MAPI og ikke rigtig kunne få hul igennem til at læse andres kalendere (mødelokalerne), så valgte jeg Exchange Webservices. Det har været stort set smertefrit. Jeg kan sende et request afsted og bede om free/busy information for 42 mødelokaler og få svar tilbage på ca 15 sekunder. Jeg tog udgangspunkt i denne kode, som jeg wrappede i en singleton, samt ændrede konfigurationen. Der var bla noget med timezone der skulle sættes. Kig på koden, den er lige til at gå til.
Eneste problem jeg har er, at somme tider ikke kan blive autentificeret mod Exchange endpointet. private ExchangeServiceBinding service = new ExchangeServiceBinding(); private ServiceSession() { this.service.Url = ServiceUrl; this.service.Credentials = CredentialCache.DefaultCredentials; this.service.RequestServerVersionValue = new RequestServerVersion(); this.service.RequestServerVersionValue.Version = ExchangeVersionType.Exchange2007_SP1; } Når jeg får denne periodiske "fejl", viser jeg et login billede og beder om username, password og domain, som jeg kan bruge til at instantiere en ny NetworkCredential med. Jeg vil tro at det hænger sammen med at mit token udløber?
Desuden har jeg måtte lave en customexception som jeg kan smide hvis ikke Exchange svarer som forventet. Jeg var udsat for at et enkelt mødelokales kalender ikke var tilgængelig. I så fald viser jeg mødelokalet med grå farve, i stedet for grøn, rød eller gul.
Deployment Jeg valgte at deploye med clickonce. Lige så snart applikationen ikke er helt trivielt, bliver det pludselig temmelig besværligt. Først var jeg ude i at jeg ville publicere applikationen på internettet. Men så fordi jeg ikke havde et offentlig udstedt certifikat, så skulle man downloade et jeg havde lavet og tilføje det under "Root Certification Authorities" før man kunne køre ClickOnce installationen. Så gik jeg over til at deploye på en intern server der er på vores domæne. Det gik fint. Indtil jeg refaktorerede add-in'et til at starte en "SplashScreen" i en ekstern proces. Så røg jeg ud i at SplashScreen.exe ikke var signet med det rigtige certifikat og dermed kunne clickonce installationen ikke gennemføres. Så jeg måtte rulle tilbage og have splashscreen'en som en del af Add-in'et. Grunden til refaktoreringen var threading i VSTO.
Hvis der var flere timer i døgnet Der er selvfølgelig flere ting som ligger lige til højrebenet at lave, når man engang får mere tid...
I denne situation er udviklingen dog ikke brugerdrevet, men udelukkende egodrevet;-)
Tips & Tricks:
For at minimere opstartstiden af Outlook, og spare de efterfølgende museklik, så kan man åbne en Appointment direkte ved at sætte "start external program" til Outlook.exe samt give nogle kommandolinie parametre med:
Du kan se alle kommandolinje parametrene her.
Jeg lavede også en testklient i ren WPF, så jeg ikke behøvede at boote outlook hver gang.
Når jeg engang er tilbage fra ferie, skal jeg nok publicere koden.
Michell Cronberg har lavet en valutakurs add-in til Excel, som du kan hente og læse om her.
God sommer!
Hva' er du kommet tilbage fra ferie? :-)
Arne, du er benhård ;-)
Jeg har været på ferie+barsel+kursus, men jeg er tilbage. Min plan var at kigge koden igennem og rydde og skrive kommentarer (den er hørt før:-) ).
Anyways, jeg har lagt koden her, skriv hvis du har nogle spørgsmål:
/Henrik