Welcome to MSDN Blogs Sign in | Join | Help

Some opinions on the computer industry

First of all, a disclaimer: These words are the opinion of Mike Fried, a software engineer and internet user, and not the official statements of Microsoft. I don't speak for Microsoft. I speak for myself. However, I think I have an interesting perspective to offer. This posting is provided AS IS with no warranties and confers no rights.

Now that we're clear on this, I was reading the "news" lately. The tagline: "Google CEO Eric Schmidt: Social networks are still too closed". The comment of Google's CEO was that, "If it's not searchable by Google, it's not open, and open is best for the consumer,". Speaking as a consumer, I strongly disagree. I jumped on the social network bandwagon and started accumulating "friends" on Friendster, MySpace, Facebook, Geni, and Linked in. If Google were suddenly given all this social networking data to crawl, without the end users' permission (i.e. my/your personal data without my/your permission), then users like me would be up in arms with Friendster, Facebook, etc. I don't want my private information searchable. (Why don't I keep my private information off the internet in general? Because I like to be able to share it with my friends, and these sites provide a mechanism for me to accept/reject who my friends are/who can see it.) I don't trust people across networks. Moreover, the social networking aspects of Friendster which mostly involved being able to introduce yourself (or your friends) to potential dates through mutual friends conflict sharply with the social networking aspects of Linked in, which involves being able to get/give referrals to jobs or learn more/provide information about companies. I might have a couple family members in my Facebook, and I might have a few family, old school buddies, and friends in Linked in, but Facebook is a social network tool/MMORPG, and MySpace is where I go when my latest rocker friend releases a new blog post or album.

Google's CEO is making the complaint that Facebook et all are closed, and I think they are rightly so. Geni being closed is probably off their radar, because it doesn't have nearly enough market share. Anyone who has used Facebook and a couple other social networks knows that what makes Facebook interesting is that the people who run it understand why we use social networks. They have the best photo sharing experience, with the ability to tag people in pictures. They have the best applications. They are the fastest growing/largest social network because they are better than the rest. And part of what makes any social network useful is the fact that the private user data is locked. Also, Facebook simply has the right features. They let you log in and keep a cookie to indefinately keep yourself connected. Others epic fail here. For example: when MySpace emails me that my friend posted a new blog and I click the link, I get sent to a rejection web page that requires me to log in, even though I've checked the box to keep me logged in forever and am already logged in right now in another web browser window. If I could take "my data" elsewhere, how would I do it, and where would I take it? Seriously, the only way to do it would be to join another social network and try to re-create my network there. Well, the different network would have different sets of experiences and have a different purpose, and not all my friends in one network really belong in the other. Not only that, but what makes Facebook a MMORPG is that people gather there and play, even if that is only "twittering" through it. Facebook recently added IM features through the web page. The way that Facebook is expanding functionality and adding features like IM through the page is quickly making it more of a "life portal". The interesting thing about social networks is that they come and they go. There was a social network in 1997 called SixDegrees. I joined it. It went away. What keeps social networks thriving? Activity. I suspect that Geni and Friendster will eventually fail (though Geni might survive). Facebook is a moving target. They have lots of features, and they keep adding new ones, like the new IM features at the bottom of the pages.

The question that is on my mind is: If Google's platform doesn't add value to Facebook, then why should Facebook consider it?

What Google is trying to do is basically become like the Trillian of social networks - the binding factor. Trillian is a remarkably good IM client because it goes deeper into features than just IM. An IM client, however is not a social network platform. The problem which should be obvious to any developer who uses more than 2 social networks is that the features of each network are similar and yet vastly different. You get the least common denominator in a platform when you bridge multiple platforms. This is ever so obvious if you try to write a program to collect the data from all your social networking sites. You will quickly discover how different each site is. The data in each is on their server in a form which gets rendered into human-readable content. The scripts building that content are subject to change at any time, and so the "protocol" which these web pages are using can change as they are human interfaces, not software contracts.

So basically, lets say that Facebook pioneered this space of social networking apps, and it's at version 3 right now, and Google is in Beta, but Google is trying to get everyone to embrace their platform. What must Google do to make OpenSocial succeed? Well, they must get Facebook onboard. Really? Yes. Facebook is the winning network both for the number of users as well as its "richness" (and I hate that word, but it seems to fit well here). I claim it is in Facebook's best interest to not join OpenSocial. It would be wonderful in theory to bridge the gap between all social networks, but in reality, I like that Facebook is the place I go to to play Texas Hold'em poker and get twitter-like status updates on friends. I like that Geni is the place I go to if I want to look at my family tree. I like that my friends who are in bands put their stuff up on MySpace where I can listen to their music. I like to be able to browse the set of people I went to school with on Friendster by School (I'm not paying classmates.com for this information, when Facebook gives it to me for free). But most of all, I like that most of my friends use Facebook, because Facebook has the best features. It's fun to play "Oregon Trail" with friends or compare movies or political opinions. It's nice to see people's photos and have embedded youtube videos on a friend's wall. Most of my friends are also on one or two other social networking sites, but in my experience Facebook is the one we all update.

So my analysis goes like this: The only way for Google to "win this battle with Facebook" for social networking application development platform is if Facebook has something to lose if they don't join Google. Since Facebook has a lead in terms of time, and is the leader in terms of platform functionality, and the leader in terms of users, I don't see Facebook joining OpenSocial. Can OpenSocial succeed without Facebook? Sure. It's possible. Do I think that Facebook will be the winner in the Social Networking space? Maybe. Maybe not. It is the clear winner at the moment. I have some issues with each social networking site that I am a member of, but "data portability" can be solved at the client side by a developer like myself writing a browser plug-in to harvest, extract, and analyze the data during visits (i.e. copy/paste). Of course, the data extracted is only as valid as the last time it was updated. If I leave Facebook, I miss the opportunity to see friend's photos and twitter-like status updates. Also, crawling is against the rules, and as Scoble discovered, lands your account disabled, but integrating into a web browser and extracting the data as the end user visits can certainly work across networks without detection (still violating the ToS/EULA), but you only get data you've already seen (basically acting as a web cache), and it gets stale as you don't visit. So Facebook has toys to make you come back and visit, and as long as you keep coming back and don't violate their agreement, you bet it will stay.

Will someone build a better social network than Facebook? It's certainly possible, but I doubt it. Overall, I think that Microsoft investing in Facebook was one of the best moves that the company has made in recent memory.

Posted by mikefried | 1 Comments
Filed under: , ,

A shoutout to a good blog.

I have been reading I.M. Wright's writings for a while now, and just recently received an email alerting me that his writings are and have been up on the web for all to see. Go here for good reads about software development.
Posted by mikefried | 0 Comments

Milestones, Cuts, and what you aren't going to ship.

At Microsoft, there is a mantra repeated often by people who have been at the company a long time: "Shipping is a feature."

Like many things in life, what you don't say is often as or more important than what you do say, and there are positive sides to this and negative sides to this mantra. In line with my last post (9 months ago - I know), I wanted to go down into the rabbit hole and take you with me on a tour of some of the indicators that you are on a team which knows how to ship. There is a corollary to "shipping is a feature", and that is:

You don't know what you are shipping unless you know what you are not shipping.

When you build a product, you have a limited number of resources and a limited amount of time from the moment that you formulate your plan to the time you sign off on the gold master to give to marketing and sell to the customer. Building a product suite as large as Office is done much the same way that building a smaller product is done: in milestones. The next several paragraphs explain how the team which builds Office works together to ensure that the right stuff gets into the final product from this perspective and how cuts happen throughout the development cycle.

Microsoft has three major divisions of labor in the "development team". These divisions of labor have layers of management reporting to one overall manager (General Manager, PUM, and VP are typical titles), and each of the "disciplines" has peak amounts of work in the beginning ("Program Manager"), middle ("Developer"), and end ("Tester"). This is generally speaking - employees work all the time during the cycle doing different tasks. In general, the PM outlines what the product needs to do, the Developer implements the product/features, and the Tester makes work for the PM/Dev when the product doesn't work as expected. The PM is also tasked with balancing the work, prioritizing features in the schedule and bugs in the **bug database.

In the beginning of the milestone, Program Managers are busy planning, producing, refining, and reviewing specs. Developmers are busy estimating work/entering estimates on work items into the schedule databases, building prototypes to answer important design and implementation questions, fixing up bugs from previous milestones or product releases (service pack and hotfix work included), and reviewing specs. Testers are busy drawing up test plans, testing bug fixes from development, writing more automated tests for previous feature work, learning the latest technology and methods, etc. Before the coding part of the milestone starts, the team meets and cuts features based on the estimates, priorities, and plans to fit the schedule.

In the middle of the milestone, Program Managers are prioritizing bugs, investigating cross-team issues, and taking care of managing the work done by teams such as localization/globalization, documentation, design, etc. Developers are writing code and tracking progress in bug databases and schedule databases. Testers are busy testing, writing automation, and filing bugs against their developers, keeping developers honest about what they actually accomplished and tracking unfinished or unanticipated work (in the bug database).

In the end of the milestone, Program Managers are prioritizing/triaging/cutting work, making tradeoffs for what bugs need potentially destabilizing changes and making decisions about how to deal with bugs which are less likely to affect the customer - these become documented and fall into categories - won't fix (ever) or postponed (maybe to the next milestone if there is one, maybe to a service pack if customers actually run into the problem and there is no readily available workaround, maybe to a next release, etc). Developers are fixing the oldest and/or worst bugs. Testers are preventing developers from checking in fixes that don't meet increasingly "higher standards" and filing bugs directly to Program Managers instead of Developers (for prioritization and pre-triage). They are also running test pass after test pass - security, performace, legacy, accessibility, localization, stress testing, etc.

The final stage of the milestone/product cycle is a code freeze which includes a triage process where the source control for the project is forked and only the very worst bugs get fixed/checked into the deliverable code. Once this stage is entered, all non-milestone blocking bugs will be postponed to the next milestone of the project. The final milestone is like an extra end to a normal milestone except that "The quality bar is raised" until only real show stoppers get fixed - the kind which would cost the company millions to recall the gold master disk. These are what I like to call "billion dollar bugs" they might cost millions to postpone, but postponing them is delaying work that cost billions. When you have no "billion dollar bugs" for your team, then you are said to be "in escrow". In the final milestone, all bugs are either fixed, triaged/postponed, documented, or resolved won't fix. Eventually, when all teams have been in escrow for some given amount of time, management signs off that the quality of the product is good, and you ship it, throw a party, take a break, and begin working on another project (the best possible time to switch teams/jobs is just after you complete a project).

That's a rough outline of what happens in a normal milestone for Office or more generally over a release cycle for a smaller product. The last couple Windows versions of the Office suite (Office XP, Office 2003, Office 2007) all had about 2-3 years of development. Since typically we have something like 6 months in a milestone, we typically had something like 4-6 milestones. At the end of a milestone, we have a "deliverable" product. Between Major Milestones (MM1, MM2, MM3, etc) we have "dogfood builds" that are internally tested and tested by some customers with special relationships. In addition to major milestones, we have Beta testing Milestones, where the deliverable is tested by many customers outside the company (Beta 1, Beta 2, Beta 2TR, etc), and finally there are shipping and maintenance milestones (RC1, RTM, SP1, SP2, etc). The point of a milestone is that it has a "deliverable", be it a dogfood build, a beta build, or a shipping product or patched product (service pack). Each milestone ends with triage. The RTM milestone is special in that it generally begins in "team triage" and ends with multiple stages of triage (multiple stages of triage allow individual teams to synchronize their level of cuts/"quality bar").

Office is a very successful team at producing a product. All of the above background is important because in it I have littered the various ways which define "what you aren't going to ship". When customers feel frustrated with product issues, bugs, design of some feature, etc. They often write with feedback (via several mechanisms) about "Why can't I achieve X by doing Y with product Z?" The answer which is most likely the case and I always feel a little frustrated that I can't give you a direct example is: "We planned to deliver Y in release Z. It was cut with change # for bug # because of reasons R1, R2, R3, and R4 which are documented in the bug database. It is still visible to the team and management."

Remember what I wrote in the beginning of this post: "Shipping is a feature." We often cut your feature Y for shipping. When it comes right down to it, no other features are as important as shipping. That's the downside of what it takes (cutting) to ship a product like Office to about 500 Million paying customers. The upside is that you have a clearer goal - you know for each stage in the product development cycle that you are doing work in a methodical, targeted fashion, and at the end of the cycle there's on the order of more than a billion reasons why you will ship. Imagine for a moment that you have a team of people the size of Office - let's say for argument sake that the numbers are on the order of 5,000 employees on average costing the company $100,000 each per year for a 2 year development cycle. So assume you have a 2 * 5K * $100K = $1B cost to develop Office.The bottom line is that it costs over $1 Billion to develop a single release of Office (today). At the end of all that work, any bug which prevents you from shipping is a billion dollar bug. Teams which cut work earlier ship better features because they spend less time overall on the things that they won't ship and more time on the things that they will. By the time that they are in their last milestone, they have fewer billion dollar bugs. Very rarely does a billion dollar bug slip through the process (when it happens, it makes news). This takes me back to the top of this message:

You don't know what you are shipping until you know what you are not shipping.

Epilogue: It is painful to spend weeks and in some cases months or years of your time working on a feature to have it cut in whole or in part, but in the end, it is better for the customer to have your imperfect product in their hands than to still be working passionately on a product they will never get because it will never be perfect or bug free. Trust me when I say that developers feel customer pain. We probably feel your pain more than you realize - we had to suffer through cuts of some really cool features in order to ship the features that you have in time to make a release date. Key danger signs that your group is not going to ship is when you talk to your senior managers and find that they don't have the ability to say, "No, we're not doing super cool feature #. We spent # time investigating it, but couldn't get it to work right, and it is cut in favor of getting more basic and more important work that we just can't ship without to be high quality and stable." Managing a successful product means knowing how to make the painful cuts necessary to ship the right product.

** Disclaimer: A bug in the context of this post means any feature of a product which works poorly from the perspective of the person who reported it to the product development team or the person in the product development team who filed the bug in the bug database.

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by mikefried | 1 Comments

The hardest part of writing successful software

The hardest part of writing successful software is legacy. Once your software succeeds, you now have precedents that you must consider. A semi-recent slashdot article points to a blogger who talks about the new OOXML formats. I don't care to get into the merits of his argument. The point is that legacy is hard. Past success is no garuntee of future success. Microsoft has some very successful products and some very unsuccessful products, and there are good people who worked on both kinds of products. In order to be successful, you need to do some things right. In order to continue to be successful, you need to continue to do things right. Is OOXML wrong or right? Time will tell.

For example, you may not have heard of Visact 2000 or Liquid Motion. Both of these products came out of the team I was originally hired into (trivia point: my business cards still say Office Activation although the current name of the team after our most recent re-org is Office Graphics). Fortunately, the technologies/people who built these products made their way into products/teams such as PowerPoint and products/teams with future potential like Expression. Will these products be successful and continue be successful? Only time will tell.

There are some people who criticize the new Office Open XML formats. As I already said, I don't want to debate them. Whether you love them, hate them, or simply don't care about them they are now part of the Office Legacy. The millions of customers already using them is evidence enough of this. I do want to make a quick point that passion about a product is a sign of success. Although you want people to love what you've done, it's hard to make something so universally good that everyone loves it. The fact that some people hate PowerPoint means that in some ways we have succeeded. Maybe in some ways we have failed - some people hate PowerPoint because they hate Microsoft for whatever reasons, and others hate something specific about PowerPoint (myself included - there are some features I just hate for my own reasons).

When new versions of the file formats come out, customers need to work with them. Unlike binary formats where the data structures and extensibility measures are locked down, XML formats can be harder to maintain. Indeed, with 6000+ pages of specs for Office Open XML there are a lot of areas to maintain. If customers can add their own extensibility tags willy nilly then there's nothing to prevent them from picking a tag name which conflicts with a tag name that we will use in the future for something else. If the conversion between binary and XML isn't perfect then loading and saving through the converters can change document content (and it happens to some extent). Also, in the future, there will undoubtedly be new features that need to be added. How well the XML changes to meet these new features without breaking and requiring rewrites will be lessons learned from the designers that came before. And that's not even mentioning bugs. I don't think we're perfect - there are bugs in those millions of lines of code that will result in someone either loving or hating the product in one version or the next. Sometimes we fix some feature in one version and break other features. I'm guilty of doing that to other people's features just as much as some other people are guilty of doing that to my features.

Another aspect to legacy is that not all of your users are modern in all ways. If your organization uses older applications and older documents written in older applications you would require new software you purchase to work with your old/existing formats. Office applications have a long history of file formats and converters. It's pretty cool how many formats Office can consume that are for products which no longer have active development but that you know someone out there depends on. People have good reason to support only the formats that they do and there is a cost-benefits analysis anyone writing converters understands (Office supports a lot of really old formats). A common theme in my recent messages is that you should do what is most valuable in your time on this earth because the opportunity cost of doing something else is not getting to do what is most valuable to you. For me that means that I have recently been spending a lot of time with my family and in particular, my 11 month old son.

The trick to deal with legacy in software is to give it respect and strive to serve as a better example in the future, because what you do leaves a legacy. Sometimes legacy is an ugly and hard problem, but without the past there can be no future. Some hacks will live on in infamy, but we learn valuable lessons from the past, and we leave valuable lessons for those who come to continue in our footsteps. My new years resolution for 2007 is to learn from past mistakes and strive to catch myself before I repeat them.

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by mikefried | 0 Comments
Filed under: ,

Good stuff in many blogs all around

I was recently looking around at some good old blog posts and some of their comments by other people.

Chris Becker in his post here links to this old post by Chris Pratley, which has a comment by Rick Schaut which begins:

   I've often referred to bugs whose ease of reproduction appears to run inversely proportional to the rigor with which one attempts to observe the cause of the bug as "Heisenberg" bugs.

I've heard them called "Heisenbugs". Heisenbugs are the reason why whenever I debug anything UI related I do it on a second console. It is the reason that unlike many other developers at Microsoft, each of my computers has its own keyboard, video, and mouse (and why I don't use KVM switches any more). My typical example of a Heisenbug goes something like this:

   "Do {some complicated operation} in the UI and {something} doesn't redraw/update properly."

This is one class of UI interaction bug that is very difficult to debug on the same console as the debugger. The reason these bugs are Heisenbugs always goes something like: you switch windows to the debugger to investigate some piece of the problem and the window switching process causes an invalidation or other messages to be pumped to the application which fixes or changes the problem. Visual Studio has the wonderful ability to Remote Debug a second machine, and that's what I use to debug just about all the time. It lets me see on one computer what the application running on the other computer is doing at a frozen moment in time. But enough on Heisenbugs (for now), I just thought I'd throw out one way of looking at the problem.

So anyway, back to my train of web links, I was looking at Rick Schaut's blog post here, and then I read this post from David Weiss which reminds me that nobody's perfect. So here I am reading Chris Pratley's latest post about why there is no OneNote viewer, which references a rather old post of his that he answers in an interesting way a few weeks later with another post about design.

So I went from reading posts about why we put the brakes on taking fixes late, to posts about features which get cut, to the design of a product. Development has a lot to do with a principle I learned in a freshman economics class called opportunity cost. Our time on this planet is limited, and what we do with that time we can only do once. There is an opportunity cost of spending that time working on one of many different challenges/problems. That cost is that you can't spend time working on anything else. If the people at Microsoft do our jobs best, we spend that time working on the most important challenges/problems first.

In simpler words: we ship with bugs (any feature you don't like is a bug). If we did our jobs right, then we have addressed the most important bugs and the only bugs left over that we ship with are the ones that you care least about. That is the theme of the blog blog posts I have been reading. I wanted to end by saying that there are lots of passionate people at Microsoft.

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by mikefried | 1 Comments
Filed under:

Future thoughts

First of all, I want to wish everyone happy holidays. I also want to give a shoutout to my friend/coworker Chris Becker who just started his own blog this week. This blog post was written from a hotel room - like many people in the greater Seattle area, my home lost power. Unlike as many, power hasn't been restored to my home yet after 3 days... most of my friends got their power back today (Sunday). We lost power at 10PM on Thursday. My 10 month old son, Benjamin got himself a cold a few days ago, so we couldn't stay in our cold, powerless house.

It has been a busy time of year for me, and I haven't been posting much. One reason is that I haven't seen any comments on my postings before the comment period expires (the comment period is designed to cut down on spam comments to seemingly forgotten posts). I have seen some people write about Office and Microsoft and what it's like shipping a product, and also about what people do after a product ships. So what does a developer do after spending the better part of a year fixing up all the issues in a shipping product for 10-20+ hours a day after several years of working on a product? Well, this developer is changing teams! Mind you, it's not a big change. It's part of a re-org. :)

I'm still involved in the development of PowerPoint, just from a slightly more shared perspective. I'm now an Office "shared team" Developer. One needs to wear more hats in a shared team than being a PowerPoint developer; Excel, Word, Outlook, and the other Office applications each have their own rich history, functionality, and requirements which make writing code that runs well across many/all of them more challenging.

So it's the holiday season, and I'm making a new years resolution to be more communicative, but I need your help! What do you want me to write about? Do you like my previous posts? It has been a long time since I had any ideas about what to post. Here's to the future.

This posting is provided "AS IS" with no warranties, and confers no rights.

Posted by mikefried | 0 Comments
Filed under:

Creating a Custom Task Pane in PowerPoint 2007

To start off a series of blog posts, I'm going to talk about creating a Custom Task Pane using PowerPoint 2007 and Visual Studio Tools for Office. Before I get into any code, I wanted to point you to the resources you can use to do this and some screencasts which you should follow along with for more detailed information of how to do some of the nitty gritty details that I don't want to cover because they are well presented. Before we begin you should already have Visual Studio 2005 and Visual Studio Tools for Office, the current beta can be downloaded for free - see this blog post (dated 9/13/2006) for details.

There are 2 screencasts demonstrating how to add a Custom Task Pane to Office applications, specifically Word and Excel. You can find them here:

1. Extending the Office 2007 UI with a Custom Task Pane
2. Creating Custom Task Panes in Visual Studio Tools for Office v3 June CTP

Later in this series I will also refer to the screencasts which show how you customize the Ribbon and demonstrate how you can make a custom XML Part and load/save it from your add-in when loading/saving PowerPoint files. This series will be in C#. You can do everything I describe here in other .NET languages as well as from any language using COM, but I personally find C# to be the easiest because it is supported by wonderful tools like Visual Studio Tools for Office. I have no reason to believe that you can't do everything below in Visual Studio Express editions and by locating the appropriate places to plug in your add-in using the Registry. Having said this, I tried it at home and decided it wasn't worth the effort. I wanted to stop for a moment to plug Collin Coller's Copy Source as HTML add-in for Visual Studio 2005. If you are a blogger looking to show example code listings, this is a great tool.

So follow the Tim Patterson's screencast (1 in the list above) and pick PowerPoint instead of Word. I named my shared add-in project "StoredSelectionPane", and I inserted a custom control into the project (rather than creating a new project) called "TaskpaneControl".

Listing 1: Connect.cs: C# code to show the "Stored Selections" Custom Task Pane in this example.

    1 namespace StoredSelectionPane

    2 {

    3    using System;

    4    using Extensibility;

    5    using System.Runtime.InteropServices;

    6    using MSO = Microsoft.Office.Core;

    7    using PPT = Microsoft.Office.Interop.PowerPoint;

    8 

    9    [GuidAttribute("549D3E43-47D0-468D-91E0-EE3690BF53A2"), ProgId("StoredSelectionPane.Connect")]

   10    public class Connect : Object, Extensibility.IDTExtensibility2, MSO.ICustomTaskPaneConsumer

   11    {

   12       private PPT.Application pptApplication;

   13       private object addInInstance;

   14 

   15       public void OnConnection(object application, Extensibility.ext_ConnectMode connectMode, object addInInst, ref System.Array custom)

   16       {

   17          pptApplication = (PPT.Application) application;

   18          addInInstance = addInInst;

   19       }

   20 

   21       public void CTPFactoryAvailable(MSO.ICTPFactory CTPFactoryInst)

   22       {

   23          MSO.CustomTaskPane ctp =

   24             CTPFactoryInst.CreateCTP("StoredSelectionPane.TaskpaneControl", "Stored Selections", Type.Missing);

   25          ctp.Visible = true;

   26       }

   27 

   28       // Unimplemented interface members

   29       public void OnDisconnection(Extensibility.ext_DisconnectMode disconnectMode, ref System.Array custom) {}

   30       public void OnAddInsUpdate(ref System.Array custom) {}

   31       public void OnStartupComplete(ref System.Array custom) {}

   32       public void OnBeginShutdown(ref System.Array custom) {}

   33    }

   34 }

I just wanted to remind people that GUIDs are unique - don't copy/paste the code on line 9 of listing 1. Instead, follow the Wizards as demonstrated in the screencasts above. The important things to note above are on lines 12 and 17, where I changed the type from object to PPT.Application (line 7 aliases PPT to Microsoft.Office.Interop.PowerPoint both to keep the namespace explicit and for convenience). Note that in order to get access to Microsoft.Office.Interop.PowerPoint, you will need to add a reference to the PowerPoint 12 COM DLL to your project. Note that line 24 tells the Custom Task Pane factory to create a Custom Task Pane with StoredSelectionPane.TaskPaneControl as the ProgId. You need to create the control and name it appropriately if you want this method call to work... The ProgId for your control goes into a special place in the Registry when you install the project. Visual Studio and Visual Studio Tools for Office handle connecting all of this for you in your install project. When you create a new user control in C#, Visual Studio generates you a class to hold all your interactions. In this case, I named my custom control TaskpaneControl and added it to the StoredSelectionPane project. The generated code for my taskpane control starts like this (I added my aliases to this file as well. We will use them throughout):

Listing 2: TaskpaneControl.cs (code editor view): The user control generated code.

    1 using System;

    2 using System.Collections.Generic;

    3 using System.ComponentModel;

    4 using System.Drawing;

    5 using System.Data;

    6 using System.Text;

    7 using System.Windows.Forms;

    8 using MSO = Microsoft.Office.Core;

    9 using PPT = Microsoft.Office.Interop.PowerPoint;

   10 

   11 namespace StoredSelectionPane

   12 {

   13    public partial class TaskpaneControl : UserControl

   14    {

   15       public TaskpaneControl()

   16       {

   17          InitializeComponent();

   18       }

We currently have a plain vanilla control. The idea I had for this control was to allow the user to save and recall shape selections. When working with large and complicated slides with hundreds of shapes, I find it useful to be able to quickly select a set of shapes which have some meaningful relationship. So with that concept in mind, we're going to add a ListView control, and some buttons. The buttons will be inside of a FlowLayoutPanel that is docked to the top of the pane and set to auto size to ensure that it is tall enough to show all controls when the user resizes the pane. The ListView control will be docked to fill the remaining space below the buttons. For buttons, I'm going to add "Add", "Remove", "Select", "Show", and "Hide". For each button, I have set AutoSize to true, AutoSizeMode to GrowAndShrink, and I have renamed the buttons from the generated names to match their text. For the ListView, I set the View type to be List (the default is Large Icon) I have turned off MultiSelect so that you can only work with one selection at a time. I have also turned off HideSelection so that when the control is out of focus you can still see the selected item. Double clicking each button creates callback methods and attaches them to their appropriate controls in the generated code. At this point, it is important to think about what we are going to do. Each entry in our ListView will be associated with a "Stored Selection". For convenience, I have written a small class to facilitate this. We will expand its functionality later, but first we need to talk about the constructor and members:

Listing 3: The StoredSelection helper class constructor.

    1    public class StoredSelection : ListViewItem

    2    {

    3       private int m_slideId = -1;

    4       private List<int> m_ids = new List<int>();

    5       public StoredSelection(PPT.Selection selection) : base("Empty Selection")

    6       {

    7          if(selection.Type != PPT.PpSelectionType.ppSelectionShapes)

    8             return;

    9          m_slideId = selection.SlideRange.SlideID;

   10          PPT.ShapeRange range = selection.HasChildShapeRange ? selection.ChildShapeRange

   11                                                              : selection.ShapeRange;

   12 

   13          string selectionName = "Shape Selection (";

   14          bool fFirst = true;

   15          foreach (PPT.Shape shape in range)

   16          {

   17             m_ids.Add(shape.Id);

   18             selectionName += fFirst ? shape.Name : ", " + shape.Name;

   19             fFirst = false;

   20          }

   21          selectionName += ")";

   22          base.Text = selectionName;

   23       }

   24    }

The StoredSelection class is called with a PowerPoint selection. Rather than associating each stored selection with an item in our list view, we will subclass the ListViewItem to store these selections inside the ListView. We set the default string to "Empty Selection". If the user of this class constructs us with a non-shape selection, this is an error case, but since we will need a valid m_slideId in order to do anything, the defaultly initialized m_slideId = -1 will ensure that in this case we won't be usable. Line 10 demonstrates that you have 2 kinds of shape selections, and we need to handle them both separately throughout to support working with children of group shapes. In order to consume our StoredSelection class so far, let's implement the AddButton callback.

Listing 4: Getting the PPT.Application and implementing the AddButton callback

    1       private PPT.Application m_app = null;

    2       public void SetApp( PPT.Application app )

    3       {

    4          m_app = app;

    5       }

    6 

    7       private void AddButton_Click(object sender, EventArgs e)

    8       {

    9          PPT.Selection selection = m_app.ActiveWindow.Selection;

   10          if (selection.Type != PPT.PpSelectionType.ppSelectionShapes)

   11             return;

   12          SelectionListView.Items.Add(new StoredSelection(selection));

   13       }

Note that for convenience, we will add a pointer to an instance of the PPT.Application interface to our Taskpane. We need to be very careful here. Do not be tempted to set m_app to "new PPT.ApplicationClass()". That will create a new instance of PowerPoint, which will keep alive your add-in after closing the instance of PowerPoint which you connected to. Go back to Connect.cs and add the following between lines 24 and 25 in listing 1 above:

         ((TaskpaneControl)ctp.ContentControl).SetApp(pptApplication);

This will let us use the application class without having to worry about new instance lifetimes keeping alive PowerPoint DLL instances on your machine after closing. Now lets implement the other buttons, starting with Remove:

Listing 5: Get the currently selected item and do something with it (part 1)

    1       private StoredSelection GetSelection()

    2       {

    3          if (SelectionListView.SelectedItems == null ||

    4             SelectionListView.SelectedItems.Count != 1)

    5             return null;

    6          return (StoredSelection)SelectionListView.SelectedItems[0];

    7       }

    8 

    9       private void RemoveButton_Click(object sender, EventArgs e)

   10       {

   11          StoredSelection selection = GetSelection();

   12          if( selection != null )

   13             SelectionListView.Items.Remove(selection);

   14       }

And now you can add and remove stored selections from the list. The rest of the buttons we will delegate the action down to some new methods on the StoredSelection class.

Listing 6: Get the currently selected item and do something with it (part 2)

    1       private void SelectButton_Click(object sender, EventArgs e)

    2       {

    3          StoredSelection selection = GetSelection();

    4          if (selection != null)

    5             selection.Select(m_app.ActiveWindow);

    6       }

    7 

    8       private void ShowButton_Click(object sender, EventArgs e)

    9       {

   10          StoredSelection selection = GetSelection();

   11          if (selection != null)

   12             selection.SetVisible(m_app.ActiveWindow, true);

   13       }

   14 

   15       private void HideButton_Click(object sender, EventArgs e)

   16       {

   17          StoredSelection selection = GetSelection();

   18          if (selection != null)

   19             selection.SetVisible(m_app.ActiveWindow, false);

   20       }

So now we just need to implement Select and SetVisible. Note that we pass along the active window to these methods. We will use the active window to search through the shapes on the slide, and do something for the shapes whose ids match those in the list. Remember that we stored the Shape.Id in the List<int> in the StoredSelection class (see listing 3 lines 4 and 17).

Listing 7: Implement StoredSelection.Select

    1       public void Select(PPT.DocumentWindow activeWindow)

    2       {

    3          if (m_slideId < 0 || activeWindow.Selection.SlideRange.SlideID != m_slideId)

    4             return;

    5 

    6          // Iterate over all shapes in the current slide

    7          PPT.Shapes shapes = activeWindow.Selection.SlideRange.Shapes;

    8          activeWindow.Selection.Unselect();

    9          foreach (int id in m_ids)

   10             foreach (PPT.Shape shape in shapes)

   11                Select(shape, id);

   12       }

   13 

   14       private void Select(PPT.Shape shape, int id)

   15       {

   16          if (shape.Id == id)

   17          {

   18             try

   19             {

   20                shape.Select(MSO.MsoTriState.msoFalse /*Replace*/);

   21             }

   22             catch (Exception e)

   23             {

   24             }

   25          }

   26          else if (shape.Type == MSO.MsoShapeType.msoGroup)

   27          {

   28             // Recurse over sub-shapes. id could be the Shape.Id of a child.

   29             foreach (PPT.Shape childShape in shape.GroupItems)

   30                Select(childShape, id);

   31          }

   32       }

There are a few important issues to consider when selecting. This implementation replaces the shape selection with a new selection built up. Note that PPT.Shape.Select will throw an exception if you attempt to select a shape which is not selectable. This is undersireable. If for example, you have a selection which includes shapes that are hidden, it would be good to select just the shapes you can select. On line 8, we clear the selection. On line 20, we add the shape whose ID matches. lines 9-11 and 29-30 ensure that we search recursively through the whole tree of shapes including iterating through groups. Finally, we implement SetVisible.

Listing 8: Implement SetVisible

    1       public void SetVisible(PPT.DocumentWindow activeWindow, bool fVisible)

    2       {

    3          if (m_slideId < 0 || activeWindow.Selection.SlideRange.SlideID != m_slideId)

    4             return;

    5 

    6          // Iterate over all shapes in the current slide

    7          PPT.Shapes shapes = activeWindow.Selection.SlideRange.Shapes;

    8          foreach (int id in m_ids)

    9             foreach (PPT.Shape shape in shapes)

   10                SetVisible(shape, id, fVisible);

   11       }

   12 

   13       private void SetVisible(PPT.Shape shape, int id, bool fVisible)

   14       {

   15          if (id == shape.Id)

   16          {

   17             try

   18             {

   19                shape.Visible = fVisible ? MSO.MsoTriState.msoTrue : MSO.MsoTriState.msoFalse;

   20             }

   21             catch (Exception e)

   22             {

   23             }

   24          }

   25          else if (shape.Type == MSO.MsoShapeType.msoGroup)

   26          {

   27             // Recurse over sub-shapes. id could be the Shape.Id of a child.

   28             foreach (PPT.Shape childShape in shape.GroupItems)

   29                SetVisible(childShape, id, fVisible);

   30          }

   31       }

This is almost identical to the previous code listing. We have one line less because we didn't need to reset the selection. The signature of these methods is slightly different so we can pass down the value to set the visibility to for all shapes in the stored selection. Note that if you have nested shapes, and you set their visibility to true, that operation does not affect the visibility of their parent group shape, but if you set the visibility of a group shape, it does set the visibility of its children (and their children).

Next time, we will address some flaws in this approach and add support to save and load custom XML for this add-in.

This posting is provided AS-IS with no warranties expressed or implied.

Posted by mikefried | 0 Comments

On account of not posting for a while...

One of the reasons I started this blog was to address what I felt are the needs of the Office technical community to get technical information that wasn't available on the other blogs. Unfortunately, most of the other blogs have been doing a better job at keeping up with producing information than I have been lately. I just wanted to reassure my readers that the time I've spent not blogging recently means that you will get a better PowerPoint 2007. I hope to resume posting useful content more regularly soon.

Posted by mikefried | 0 Comments

PowerPoint Table Styles XML (Part 2)

This time we will look at the table style portion of PresentationML in the Office Open XML Format and we will go through many of the finer details of how to make one. To get PowerPoint to generate the XML for a built-in table style, you simply insert a Table and Save as PPTX. Rename your PPTX file to .ZIP, and look under ppt\tableStyles.xml. You will see something resembling the following:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<a:tblStyleLst xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main" def="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}">
   <a:tblStyle styleId="{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}" styleName="Medium Style 2 - Accent 1">

Colors and formatting here are from Visual Studio, but you can use any old text editor such as notepad to edit these XML files. The default table style here is specified with the GUID {5C22544A-7EE6-4342-85BDC9FD1C3A}. It is the only style listed in the list above. Let’s start by removing it from the list and making a new custom style. Create a new GUID (either using a GUID Generating tool or an online GUID Generator). For example, I used the online GUID Generator and got: {90651C3A-4460-11DB-9652-00E08161165F}. You do not need to capitalize all the hexadecimal letters (but I do so because earlier beta versions required that your GUIDs be in capital hexadecimal). Let’s start with a minimal style. Here it is:

<a:tblStyle styleId="{90651C3A-4460-11DB-9652-00E08161165F}" styleName="Empty Custom Style" />

It doesn’t do much, but it gets the point across. Save your modified tableStyles.xml, copy your ZIP file to a new PPTX, apply your custom style to the table, and PowerPoint will show you the fruits of your labor:

What is the point of customizing a style if you don’t do anything with it? Well, it lets you see what the default values are. Generate a new GUID and we can start a real example:

<a:tblStyle styleId="{put your GUID here}" styleName="Example Custom Style 1">


We will start out by setting the background:

   <a:tblBg>
      <a:fillRef idx=
"3">
         <a:schemeClr val="accent1">
            <a:tint val="50000"/>
         </a:schemeClr>
      </a:fillRef
>
      <a:effectRef idx=
"2">
         <a:schemeClr val="accent2"/>
      </a:effectRef>
   </a:tblBg>

Note that we are setting the fill to refer to the style matrix with index = 3 (intense fill) and the effect to refer to the style matrix with index = 2 (moderate effect). This means that our custom style will appear different when the user changes themes. See Howard’s post on the topic for more info. These indices refer to the columns in “Style Matrix” from Howard’s post (0 = no line/fill/effect, 1 = Subtle, 2 = Moderate, 3 = Intense):

Note: If you wanted to give a specific appearance to your tables, you do not have to use themed elements (fonts, fills, lines, and effects) and instead can specify your own fonts, fills, lines, and effects. You are not limited to the colors from the color scheme. Scheme colors are one of 6 ways to specify colors.

Now that we have set the background fill/effect for the whole table, we will set properties which apply to all cells in the table (whole table part style). This part defines the default appearance for all of the cells in the table in their default state. The first thing we set is the table cell text style (tcTxStyle):

   <a:wholeTbl>
      <a:tcTxStyle
>
         <a:fontRef idx=
"minor"/>
         <a:schemeClr val="dk1"/>
      </a:tcTxStyle>

We pick the minor font (used for text – the major font would be used for headings – this is discussed a few times in Howard’s posts). We use the dk1 (Dark  1) color for our text. Notice that we tinted our table background color by 50% above. Tint makes the color lighter. Shade makes the color darker. By tinting the background color by 50%, we make it contrast more with our dark 1 colored text. There are other good choices here, and I wanted to highlight that tx1 (Text 1) maps to either dk1 (Dark 1) or lt1 (Light 1), and bg1 (Background 1) maps to the contrasting color (same for tx2, bg2, dk2, and lt2). You set which light/dark pair are used with the background style gallery in the design tab. If your table style has no fill, then using tx1 or tx2 will make your text more likely to contrast against the slide background (but this is not a guarantee – the slide background may have images, there may be shapes on the master, or you can have other shapes under your table). If you wanted to always ensure that your text will contrast and still show design elements under it, you can use a semi-transparent fill (see below) of bg1 or bg2 to mute the background or other slide content under your table. I recommend using an un-themed solid fill in this case.

Next we will set the line styles for the outer edges of the table with accent 3 and the “subtle” line style from the theme. Note that these lines are the lines around and inside the “whole table” part. Top here is a continuous single line from the top left to the top right (in Left-to-Right language tables). We empty out the inner line styles (the diagonals are empty by default, but the inner horizontal/vertical lines are set by default). I have collapsed these XML elements on individual lines to save space.

      <a:tcStyle>
         <a:tcBdr
>
            <a:left><a:lnRef idx=
"1"><a:schemeClr val="accent3"/></a:lnRef></a:left>
            <a:right><a:lnRef idx="1"><a:schemeClr val="accent3"/></a:lnRef></a:right>
            <a:top><a:lnRef idx="1"><a:schemeClr val="accent3"/></a:lnRef></a:top>
            <a:bottom><a:lnRef idx="1"><a:schemeClr val="accent3"/></a:lnRef></a:bottom>
            <a:insideH><a:ln><a:noFill/></a:ln></a:insideH>
            <a:insideV><a:ln><a:noFill/></a:ln></a:insideV
>
         </a:tcBdr>

After specifying the border line styles, we define a fill for all the cells in the table. Note that cells are drawn on top of the table background. Since we already have a contrasting set of colors for the table background and the default text color, we really don’t need to tweak this part. The default is “no fill”, but I’m going to set it explicitly just to indicate how it is done:

         <a:fill>
            <a:noFill
/>
         </a:fill
>
      </a:tcStyle>

And that’s it for the whole table part. Let’s close the table style tags now.

   </a:wholeTbl>
</a:tblStyle>

Save the XML file, copy the ZIP to a new PPTX file, and load it into PowerPoint. After applying the new table style, we see should see something like this:

Notice that there are grid lines showing in the table in the document, but no gridlines showing in the thumbnail. The default setting in the Table Tools/Layout tab is to show gridlines when editing the table. This setting is not table or document specific.

We haven’t specified the top row, bottom row, or any other part. Users of our table style will want to be able to express subtle or not-so-subtle structure in the data that they enter into their tables. Our design is not yet done. We don’t need to change every part, but at the moment we have a very uninteresting style.

The next set of parts defined in the table are the horizontal and vertical bands. “band1H” applies to every “odd” row starting after the first (also called header) row and before the last (also called total) row (if the header or total row parts are applied) and “band2H” applies to the “even” rows. Similarly, “band1V” applies to every “odd” column after the first column (left column in left-to-right languages) and before the last column (right column in left-to-right languages) and “band2V” applies to the “even” columns.

For this example table style, remember that the background fill is using dark 1 for the text color and accent 1 tinted to be 50% brighter as color for the intense theme filled background. Let’s change the background fill and text color for odd rows and lets make the text italic for odd columns. Insert the following before the close tag for our table style:

   <a:band1H>
      <a:tcTxStyle
>
         <a:schemeClr val=
"lt1"/>
      </a:tcTxStyle>
      <a:tcStyle
>
         <a:tcBdr
/>
         <a:fill
>
            <a:solidFill
>
               <a:schemeClr val=
"dk1">