• The Old New Thing

    Thinking through a feature

    • 75 Comments

    The commentary after my entry on taskbar grouping drifted into people asking for still more features in taskbar grouping.

    Writing the code is the easy part.

    Designing a feature is hard.

    You have several audiences to consider. It's not just about the alpha geeks; you have to worry about the grandmothers, the office workers, the IT departments. They all have different needs. Sometimes a feature that pleases one group offends another.

    So let's look at some of the issues surrounding the proposed feature of allowing users to selectively ungroup items in the taskbar.

    One issue with selective grouping is deciding the scope of the feature. Suppose the user ungroups Internet Explorer, then closes all the IE windows, then opens two new IE windows: Do the new ones group?

    If so, then you now have an invisible setting. How do you configure grouping for programs that aren't running? (How do you configure something that you can't see?)

    Suppose you've figured that out. That's fine for the alpha geeks, but what about grandma?

    "The Internet is all disorganized."

    "What do you mean?"

    "All my Internet windows are all disorganized."

    "Can you explain a little more?"

    "My taskbar used to be nice and organized, but now the Internet parts are disorganized and spread out all over the place. It used to be nice and neat. I don't know how it happened. I hate the Internet, it's always messing up my computer."

    What is the UI for selective ungrouping? Anything that is on a context menu will be executed accidentally by tens of thousands of people due to mouse twitching. Putting the regroup onto the context menu isn't necessarily good enough because those people don't even realize it was a context menu that did it. It was just a mouse twitch.

    Mouse twitches cause all sorts of problems. Some people accidentally dock their taskbar vertically; others accidentally resize their taskbar to half the size of the screen. Do not underestimate the havoc that can be caused by mouse twitching.

    Soon people will want to do arbitrary grouping. "I want to group this command prompt, that notepad window, and this calc window together."

    What about selective ungrouping? "I have this group of 10 windows, but I want to ungroup just 2 of them, leaving the other 8 grouped together."

    Once you have selective/arbitrary grouping, how do you handle new windows? What group do they go into?

    Remember: Once you decide, "No, that's too much," there will be thousands of people cursing you for not doing enough. Where do you draw the line? And also remember that each feature you add will cost you another feature somewhere else. Manpower isn't free.

    But wait, the job has just begin. Next, you get to sit down and do the usability testing.

    Soon you'll discover that everything you assumed to be true is completely wrong, and you have to go back to the drawing board. Eventually, you might conclude that you over-designed the feature and you should go back to the simple on/off switch.

    Wait, you're still not done. Now you have to bounce this feature off corporate IT managers. They will probably tear it to shreds too. In particular, they're going to demand things like remote administration and the ability to force the setting on or off across their entire company from a central location. (And woe unto you if you chose something more complicated than an on/off switch: Now you have to be able to deploy that complex setting across tens of thousands of computers - some of which may be connected to the corporate network via slow modems!)

    Those are just some of the issues involved in designing a feature. Sometimes I think it's a miracle that features happen at all!

    (Disclaimer: I'm not saying this is how the grouping feature actually came to be. I just used it as a starting point for a rant.)

    For another perspective, you can check out KC Lemson's discussion of the feature-design process a few days ago under the topic There's no such thing as a simple feature.

  • The Old New Thing

    Norway: Pros and cons

    • 13 Comments

    For the third year in a row, the United Nations' Development Program has ranked Norway as having the best standard of living in the world. Norwegians beat out all others because of their high levels of education, pay and life expectancy.

    In a totally unrelated story: Norway world leader in casual sex.

    Of course, Norway isn't all candy canes and lollipops. You also have to worry about cow attacks and cows falling from the sky.

    Cows rampage in Norway

    In a bizarre series of incidents, two farmers in different parts of the country were hospitalized after being attacked by cows. Elsewhere, four men narrowly missed having their car crushed by a crash-landing cow.

  • The Old New Thing

    Is your web site an open relay?

    • 20 Comments

    As if there isn't enough to worry about.

    Everyone knows about the dangers of open SMTP relays. But how many people realize the dangers of an open HTTP relay?

    Many web sites do arbitrary redirection. If I were a spammer, I could create a link to myself that redirects through some well-known web sites, thereby granting my spam link false authenticity.

    http://rds.yahoo.com/*-http://weblogs.asp.net/oldnewthing/
    http://ads.msn.com/ads/adredir.asp?url=/oldnewthing/

    With some obfuscatory work, I can make my own URL disappear completely, leaving just yours.

    http://rds.yahoo.com/*-http://%77e%62l%6fg%73.%61s%70.%6ee%74/%6fl%64n%65w%74h%69n%67/
    http://ads.msn.com/ads/adredir.asp?url=http://%77e%62l%6fg%73.%61s%70.%6ee%74/%6fl%64n%65w%74h%69n%67/

    What does this mean for you, the redirector?

    • It creates additional load on your server.
    • It makes Bayesian filters think that your site is spam, since your site's name appears in spam URLs. This can cause problems if you intend to send legitimate mail to your customers.
    • It can fool people into thinking that your site is the source of the spam.

    The two examples I gave above are the big guys who are being attacked by spammers on all sides. In fact, their names are co-opted by spammers so much that some spam redirection URLs probably don't affect their Bayesian rating significantly. But if you're a small site that also has unchecked redirection, you may want to take a closer look.

  • The Old New Thing

    Varför läser jag svenska?

    • 16 Comments

    (Even if you don't read Swedish, you may enjoy the links at the end.)

    Några personer har frågat mig varför jag läser svenska.

    Historien börjar för många år sedan. Jag läste tidningen och såg att det fanns avgiftsfria norskalektioner i Ballard kyrkans källaren. (Ballard är en del av Seattle där många människor som kommer från Norden bor. Den mesta är norska.) Jag tänkte mig att det skulle vara roligt om jag gick till klassen. Jag skulle säkert vara den enda personen som inte var tvungen att läsa norska av sina föreldrar.

    Men jag gjorde inte det.

    Ett par år senare tillkom de olympiska spelen i Lillehammer, och när jag hörde det norska språket, tyckte jag att det lät skönt och att jag skulle börja läsa norska.

    Men jag gjorde inte det.

    I förra året flyttade min vän till Uppsala för att studera på universitet som utbytesstudent. Jaha, nu måsta jag lära mig svenska för att besöka honom! Men innan min första klass sa hans bror till mig, "Jag har ett arbetsmöte i Mnchen nästa vecka. Vill du komma med? Vi kan besöka Jonathan i Uppsala." Det var min nödssemester.

    Jag hade redan betalat för den svenska kursen, så jag gick till klasserna i alla fall. Sex månader senare reste jag till Sverige igen.

    Nu har jag inga bestämda planer på att resa dit en gång till, och jag kommer nog att ta en paus från att läsa svenska i utgången av sommar, så att jag kan börja att läsa kineska. (Jag tror att kineska kommer att vara svårare än svenska...)

    Om jag hade sökt på Internetet skulle jag ha funnit andra skäl. En Google-sökning avslöjar att "When you win the Nobel Prize, you can give your acceptance speech in Swedish!" Men de glömde att ange att det inte gäller om du vinner Nobelfredspriset. I så fall har du ödslat tid på det felaktiga språket! (Det upptäckte jag i förra året när jag bestämde mig för att vara Nobelfredspriset på allhelgonaafton...)

    (Ett annat skäl: Svenska finns inte på Babelfish.)

    Jag tycker att det är intressant att en Google-sökning efter "Why study Swedish?" hittar bara 12 webbsidor. Jämför "Why study French?", som finns på 700 webbsidor, ungefär den samma mängden som engelska.

    Förresten, "Why study at all?" finns på 20 webbsidor. Undrar du ännu varför inte många människor läser svenska?

  • The Old New Thing

    How do the FILE_SHARE_* bits interact with the desired access bits?

    • 19 Comments

    It's really not that complicated. If you permit, say, FILE_SHARE_READ, then you're saying, "I'm okay with other people reading this file while I have it open." And if you leave off the flag, then you're saying, "I do not want other people reading this file while I have it open."

    Now all that's left to do is work out what that means.

    So suppose you omit the flag, indicating that you don't want to let others read the file. Then when you attempt to open it yourself, the open will fail if anybody else has the file open for reading. And if the open succeeds, then the system will prevent anybody else from opening the file for reading until you close your handle.

    That's all.

    Of course, if the file is already open, then a corresponding check is made between your desired access and the file sharing mode of the people who already opened it. For example, if somebody already has the file open and denies read sharing, then if you try to open for read, you will get a sharing violation.

    These restrictions are cumulative, of course. If one person opens a file without FILE_SHARE_READ and another person opens a file without FILE_SHARE_WRITE, then attempts to open the file for read or for write will fail. (The read fails because the first person didn't permit read, and the write fails because the second person didn't permit write.)

    Repeat the above logic for "delete" and "write" permission, and that's it in a nutshell.

    There is a big nasty table in MSDN that walks through all the combinations, but I personally think it confuses the matter rather than clarifying.

    Even more confusingly, the table uses "X" to mean that the combination is permitted. They should've used a bullet ("•") or a check mark ("ü"), since an "X" has the connotation of "not allowed".

    Let's look at one row of the table and see how the information in it is "obvious": Say, the row that reads "GENERIC_READ / GENERIC_WRITE / FILE_SHARE_READ". You are asking for read and write, and you permit read (and implicitly deny write).

    The requested access (read/write) requires that all previous openers have granted both read and write. There are three columns that correspond to this, namely the ones that say "FS_R FS_W".

    The requested sharing mode (read only) requires that all previous openers have requested read-only access. In other words, there can't be any G_W entries. That rules out two of the columns, leaving just "G_R FS_R FS_W", and indeed only one column is checked in the table.

    Notice that the file share bits you pass don't have to match up with your file access bits. The file share bits indicate what you want to allow other people to do. The access bits indicate what you want to do yourself.

  • The Old New Thing

    Links about COM threading models

    • 2 Comments

    There have been a few good articles about threading models, one from Eric Lippert and another from Larry Osterman. Go off and read them if you haven't already.

  • The Old New Thing

    There are two types of scrollbars

    • 26 Comments

    Remember that there are two types of scrollbars.

    One is the standalone scrollbar control. This one has its own window handle, and consequently can be given focus and all those other fun things you can do with window handles. To manipulate them, pass the handle to the scrollbar control to the appropriate scrollbar function (SetScrollInfo, for example) and pass SB_CTL as the nBar parameter to indicate that you have a scrollbar control.

    The other type is the horizontal or vertical scrollbar (or both) attached to a window by virtue of having the WS_HSCROLL and/or WS_VSCROLL style. These are nonclient scrollbars and are not controls. They are just decorations added to some other window. You can't give them focus since they aren't windows in their own right. To manipulate them, pass the handle to the containing window to the appropriate scrollbar function and pass SB_HORZ or SB_VERT as the nBar parameter to indicate that you want to manipulate the nonclient horizontal or vertical scrollbar.

    I'm writing this down since some people seem to miss the distinction between these two cases.

  • The Old New Thing

    A guide to British pub etiquette

    • 25 Comments

    Social researchers dissect the interactions and unveil the unwritten rules that govern British pub etiquette.

  • The Old New Thing

    Image File Execution Options

    • 5 Comments

    Hereby incorporating by reference Junfeng Zhang's discussion of the Image File Execution Options registry key.

  • The Old New Thing

    When should your destructor be virtual?

    • 20 Comments

    When should your C++ object's destructor be virtual?

    First of all, what does it mean to have a virtual destructor?

    Well, what does it mean to have a virtual method?

    If a method is virtual, then calling the method on an object always invokes the method as implemented by the most heavily derived class. If the method is not virtual, then the implementation corresponding to the compile-time type of the object pointer.

    For example, consider this:

    class Sample {
    public:
     void f();
     virtual void vf();
    };
    
    class Derived : public Sample {
    public:
     void f();
     void vf();
    }
    
    void function()
    {
     Derived d;
     Sample* p = &d;
     p->f();
     p->vf();
    }
    

    The call to p->f() will result in a call to Sample::f because p is a pointer to a Sample. The actual object is of type Derived, but the pointer is merely a pointer to a Sample. The pointer type is used because f is not virtual.

    On the other hand, the call to The call to p->vf() will result in a call to Derived::vf, the most heavily derived type, because vf is virtual.

    Okay, you knew that.

    Virtual destructors work exactly the same way. It's just that you rarely invoke the destructor explicitly. Rather, it's invoked when an automatic object goes out of scope or when you delete the object.

    void function()
    {
     Sample* p = new Derived;
     delete p;
    }
    

    Since Sample does not have a virtual destructor, the delete p invokes the destructor of the class of the pointer (Sample::~Sample()), rather than the destructor of the most derived type (Derived::~Derived()). And as you can see, this is the wrong thing to do in the above scenario.

    Armed with this information, you can now answer the question.

    A class must have a virtual destructor if it meets both of the following criteria:

    • You do a delete p.
    • It is possible that p actually points to a derived class.

    Some people say that you need a virtual destructor if and only if you have a virtual method. This is wrong in both directions.

    Example of a case where a class has no virtual methods but still needs a virtual destructor:

    class Sample { };
    class Derived : public Sample
    {
     CComPtr<IStream> m_p;
    public:
     Derived() { CreateStreamOnHGlobal(NULL, TRUE, &m_p); }
    };
    
    Sample *p = new Derived;
    delete p;
    

    The delete p will invoke Sample::~Sample instead of Derived::~Derived, resulting in a leak of the stream m_p.

    And here's an example of a case where a class has virtual methods but does not require a virtual destructor.

    class Sample { public: virtual void vf(); }
    class Derived : public Sample { public: virtual void vf(); }
    
    Derived *p = new Derived;
    delete p;
    

    Since the object deletion occurs from the pointer type that matches the type of the actual object, the correct destructor will be invoked. This pattern happens often in COM objects, which expose several virtual methods corresponding to its interfaces, but where the object itself is destroyed by its own implementation and not from a base calss pointer. (Notice that no COM interfaces contain virtual destructors.)

    The problem with knowing when to make your destructor virtual or not is that you have to know how people will be using your class. If C++ had a "sealed" keyword, then the rule would be simpler: If you do a "delete p" where p is a pointer to an unsealed class, then that class needs have a virtual destructor. (The imaginary "sealed" keyword makes it explicit when a class can act as the base class for another class.)

Page 389 of 426 (4,251 items) «387388389390391»