Tuesday, January 11, 2011
Why Adaptative Menus Don't Work (and the Ribbon UI neither)
A while back, Microsoft decided that adaptive menus, which adapt to only show the features users use the most, were a good idea. This feature debuted with Office 2003 and Windows XP.
I think I can confidently say that most users hated them, and I was one of them. Why is that?
The problem with adaptive menus is that the UI decides which menu entries to display using its own unfathomable intelligence. This surprises the user, which is a big no no in Software Design. Furthermore, the user has to learn how to display the hidden menu entries.
But the biggest flaw adaptive menus have is that they shortcut spatial memory. For example, when I need to insert a picture into my favorite (or not so favorite) text editor, I think about about where spatially the menu is located in the menu structure and point to that area. Then around that space I try to find the entry with the matching words Insert Picture. Adaptive menus don't give a damn about this way of proceeding.
Thankfully Microsoft finally gave in and turned off adaptive menus by default in later versions of their Office programs.
The Empire Strikes Back
Not wanting to accept defeat, Microsoft stroke back with another monstrosity: the Ribbon UI.
The Ribbon UI happily consumes screen real estate for no other apparent reason than to annoy the user. It shamelessly throws the previous spatial setup and order of menu features in disarray. The first time I started Word 2007 I think I searched for half an hour where the file menu had gone. Ha! Long-time users? We don't give a damn about long-time users...
But the biggest problem with the Ribbon UI is that it suffers from the same problem as the adaptive menus: again it doesn't care about spatial memory. Several features are located in the same space of the rectangular area delimited by the ribbon UI. To find a feature, you need to do a kind of modal switch between the tab pages of the Ribbon UI, and you need to remember which tab page containing the feature you are interested in is associated with which tab caption.
If this sounds complicated, well compared with the simplicity of good old menus it certainly is.
Return of The Jedi?
One can only hope that in its future incarnation of software products, Microsoft will either completely diss personalized menus or the Ribbon UI (very unlikely), or at least give the user the option to choose which UI he wants to use. Only time will tell...
Wednesday, March 10, 2010
C# : How to Wrap Word InterOp COM Methods
For example, the Word.Documents.Open() method takes 16! parameters in the Interop v 12 classes. You don't want to have to specify those at each method call.
So what do you do? Well C# has a nice feature called extension methods since the version 3 of the .net Framework. You will leverage those to give you nice methods to call with few parameters.
So you define a static class that will contain the extension methods on the various COM Interop classes you are interested in. For example, for the Open() method discussed above, see how I wrap it with my extension method:
using Microsoft.Office.Interop.Word;
namespace MyWordNamespace {
public static class ExtensionMethods {
public static Document Open(
this Documents documents,
string fileName) {
Object filename = fileName;
Object confirmConversions = Type.Missing;
Object readOnly = Type.Missing;
Object addToRecentFiles = Type.Missing;
Object passwordDocument = Type.Missing;
Object passwordTemplate = Type.Missing;
Object revert = Type.Missing;
Object writePasswordDocument = Type.Missing;
Object writePasswordTemplate = Type.Missing;
Object format = Type.Missing;
Object encoding = Type.Missing;
Object visible = Type.Missing;
Object openConflictDocument = Type.Missing;
Object openAndRepair = Type.Missing;
Object documentDirection = Type.Missing;
Object noEncodingDialog = Type.Missing;
Object xmlTransform = Type.Missing;
return documents.Open(
ref filename,
ref confirmConversions,
ref readOnly,
ref addToRecentFiles,
ref passwordDocument,
ref passwordTemplate,
ref revert,
ref writePasswordDocument,
ref writePasswordTemplate,
ref format,
ref encoding,
ref visible,
ref openAndRepair,
ref documentDirection,
ref noEncodingDialog,
ref xmlTransform
);
}
}
}
Which you can then call like this (I've also added wrappers for Documents.Close() and Application.Quit()):
using Microsoft.Office.Interop.Word;
using MyWordNamespace;
namespace MyApplication {
class Program {
static void Main(string[] args) {
Application application = new Application();
try {
application.Visible = true;
Document document = application.Documents.Open(
@"c:\temp\Document.docx"
);
Thread.Sleep(5000);
application.Documents.Close();
}
finally {
application.Quit(WdSaveOptions.wdDoNotSaveChanges);
}
}
}
}
Nice, isn't it?
Happy programming!
Friday, February 26, 2010
Linq To Sql: Handle a transaction deathlock
while (true) {
try {
using (var scope = new TransactionScope()) {
using (var db = new MyDataContext()) {
// Do what you need to do on the DB...
db.SubmitChanges();
}
scope.Complete();
}
break;
}
catch (SqlException e) {
if (e.Number == 1205) {
Thread.Sleep(new Random.Next(1000));
}
else {
throw;
}
}
}
Thursday, February 11, 2010
C#: Linq To Sql: Updating a reattached entity
The first step is to make sure that the underlying table has a timestamp column. Then you need to make sure that the entity object takes this into account. The best is to only create the entity in Visual Studio's designer after the timestamp column has been added. If it already exists, delete it from the designer, then drop it from the Server Explorer to the designer again.
Now that this basic infrastructure is in place, you only need to make sure that when you get an entity from a DataContext, and that entity will be updated and reattached to another DataContext later, the initial DataContext should have the EnableDeferredLoading set to false. You can then reattach to the second DataContext with the Attach
Example:
Product product;
using (var db = new MyDataContext())
db.DeferredLoadingEnabled = false;
product = db.Products.SingleOrDefault(p => p.Id = "1");
}
product.Name = "Updated Name";
using (var db = new MyDataContext())
db.Attach(product, true);
db.SubmitChanges();
}
VoilĂ , you are done!
Wednesday, December 2, 2009
C#: Use verbatim strings for SQL queries
This one?
string query = @"
SELECT
User.FirstName,
User.LastName,
User.Age,
User.Gender,
User.HairColor,
Adress.Street,
Adresse.Zip,
Adress.City,
Adress.Country
FROM
User
INNER JOIN
Adress ON User.AdressId = Adress.Id
SORT BY
User.FirstName,
User.LastName
";
or this one?
string query = "SELECT User.FirstName, User.LastName, User.Age, "
+ "User.Gender, User.HairColor, Adress.Street, Adresse.Zip, "
+ "Adress.City, Adress.Country FROM User INNER JOIN Adress ON "
+ "User.AdressId = Adress.Id SORT BY User.FirstName, "
+ "User.LastName";
The SQL engine doesn't care for the whitespace you introduce in the queries with the verbatim strings. So do your fellow programmers a favor and use them for your SQL queries!
Tuesday, December 1, 2009
C#: Multithreading IS simple
OK, this attention-grabbing title is a little bit fueled by my frustration of having seen badly written multi-threading code once too often.
Multi-threading code has a reputation of being difficult to read and to write, and it's true that there are a lot of things to consider once you go down that route. However, I would also say that a large amount of the errors one sees in multi-threading code is due to the fact that no synchronization is used at all.
So, the basic multi-threading principle is surprisingly simple: If two (or more) threads access the same piece of data, you MUST synchronize access to it. This is true even if your PC has only one processor, as current processors are often multi-core.
In C#, if several threads access some simple (value type) data, one typically uses the lock() statement, like the example below, where access to the Count property is thread-safe:
public class MultiAccess {
private readonly object syncObject = new object();
private int count = 0
public int Count {
get {
lock (syncObject) {
return count;
}
}
set {
lock (syncObject) {
count = value;
}
}
}
}
syncObject is an object used solely for the purpose of synchronization, making it readonly ensures that it's reference can never be changed by malicious code. Obviously, there is more to multi-threading than that, but the above will nicely take care of the basics.
That's it, happy programming
