April 2007 - Posts
I have come to the realization, at some point, that I never want to close Outlook. Really, there's no reason for me to shut it down except when doing something special, like freeing every available byte of memory, or when developing add-ins to it. During day-to-day work, I always want it on and minimized. The problem is that there's no easy keyboard shortcut to minimize Outlook like there is to close it - I don't like the two-step shortcut of Alt+Space - n, and I don't want to define a new non-standard keyboard shortcut that I have to remember in addition to the standard Windows keys.
So instead, I've decided to go the route that RSSBandit and other programs did - intercept the Alt-F4 keypress, and minimize to the tray rather than closing. Whenever I do want to close Outlook, I can do it via the File->Exit option. It's rare enough not to bother me.
Writing this proved to be ridiculously easy when using VSTO 2005 SE and a library I had already developed a while ago to hook into the keyboard events.
The steps are simple:
1) Create the Outlook Add-in Project. I wrote this for Outlook 2007 - it should work the same for 2003:
2) Add a reference to the HookLibrary mentioned above.
3) Write the following code in the ThisAddin_Startup event:
private KeyboardHook altF4Hook;
private void ThisAddIn_Startup(object sender, System.EventArgs e)
altF4Hook = new HookLibrary.KeyboardHook();
// false/true/false is for ctrl/alt/shift modifiers, respectively.
altF4Hook.AddFilter(Keys.F4, false, true, false);
altF4Hook.KeyPressed += new KeyboardHookEventHandler(altF4Hook_KeyPressed);
void altF4Hook_KeyPressed(object sender, KeyboardHookEventArgs e)
Outlook.Explorer exp = Application.ActiveExplorer();
exp.WindowState = Microsoft.Office.Interop.Outlook.OlWindowState.olMinimized;
And that's it. On our development machine it should automatically be installed in Outlook. For other machines, we can build the MSI file that's created for us and install it. I've attached the MSI here, as well as the full code. Enjoy.
Another odd client request that came in a while ago.
When we browse our Sharepoint site, IE's title reads "Home - My Site Name - Windows Internet Explorer". We can't find any way to remove the "Home" string, regardless of how we change the site's name. This is critical for our ISO review tomorrow!
Now, I'm no ISO reviewer, but it seems a bit excessive even for the strictest ISO test. But hey, I'm just your friendly neighborhood technical solutions provider.
The first step to changing it is to understand where this string is stored. Easiest way to do that is to open the default.aspx page with Sharepoint Designer. We can see it contains a content block that replaces the master page's Title content placeholder.
This is the master page bit (line 14 on my unedited default.master):
<Title ID=onetidTitle><asp:ContentPlaceHolder id=PlaceHolderPageTitle runat="server"/></Title>
And this is the replacement in default.aspx:
<asp:Content ContentPlaceHolderId="PlaceHolderPageTitle" runat="server">
<SharePoint:EncodedLiteral runat="server" text="<%$Resources:wss,multipages_homelink_text%>"
<SharePoint:ProjectProperty Property="Title" runat="server"/>
We can see that the title is made up of two parts, the localized "Home" string (in red) and the current site's name (in blue) taken from the site's properties. We have two options here - we can either find the resource string and change it, or remove it from the ASPX altogether (along with the connecting dash) and have a clean page title. The second option is pretty straightforward, so we'll focus on the first:
We can see that the actual text is retrieved from some Resource Manager. The format of the $Resources token is as follows:
So what we need is the string "multipages_homelink_text" under the file wss.resx. Finding it, however, took me a while. I found the file under 12\CONFIG\Resources (under the base 12 folder) rather than the more obvious 12\RESOURCES. This folder contained wss.resx, and also wss.en-US.resx and wss.he-IL.resx, the two languages I had installed. Seemed fitting. Editing the multipages_homelink_text value in them, however, didn't seem to change anything. So I expanded the scope again.
Seems that resx files in the 12 directory are only template files, copied to each specific Virtual Server when its created. From there on, the virtual server has its own copy of the resx files, residing under C:\inetpub\wwwroot\wss\VirtualDirectories\<port>\App_GlobalResources. There I found another copy of the wss.resx file, and changing the value there instantly updated the site, no IISRESET necessary. Joy!
I'm a relative latecomer to the unit-testing business, and only got to play with Mocking frameworks in my last project.
After testing several frameworks, we opted on RhinoMocks for emulating our classes during testing. This, coupled with a lot of IOC and dependency injection, made our rather complex, distributed system much easier to test. Code everything to interfaces, feed them in via constructors or whatnot, and you can now slip in a fake cache or project-data service and test only the issue at hand.
Now, I still have issues with IOC/Dependency Injection. While it makes testing easier, and enables replacing modules with little hassle, it really complicates the code. One of the nice things about .NET code is how straightforward it is - less plumbing code, metadata separated out to attributes, now we know that the code to our method is much cleaner and more of it actually describes the program logic. Once we start injecting dependencies, things get muddled. Now we have code that uses the various dependencies given to it, but we don't know where it was created and what actual concrete object it actually is. When everything is interfaces, and the concrete classes themselves may actually be created dynamically with reflection at runtime, there's no way to read the code and use the Visual Studio code-browsing tools to know what exactly is going on.
I'm not saying we should dump the IOC concepts - they certainly have their uses - but before throwing them into a project we should be aware of their higher learning curve and added complexity, and see whether our project's scope really needs it. Not every project is a distributed system with dozens of entities talking to each other.
Did I just write all that? All I really wanted to say is that when using RhinoMocks, we were limited in mocking our Configuration Service, since it relied heavily on generic methods to return its data - couldn't mock generic methods and had to create a concrete "fake" configuration service for use in testing. But now RhinoMocks v3 has been released, with support for mocking generic methods. I am no longer part of that project, but the guys down there say they've been busy pushing the new feature into their unit tests. Congrats, Ayende! This is one tool I'll be taking with me to new projects.