Archives
-
Applying Quotas Across all My Sites
Just a quick snippet this morning. If you need to apply a new quota template to all users My Sites here's a quick script to do it. Changing an existing quota is fine but if you're migrating users from another system or you just want to up everyone's storage a bit here's what you do.
- Create a new quota template. This is found in Central Admin under Application Management | Site Collections | Specify quota templates. There's already a default "Individual Quota" created you might want to create your own or have a special one for your users
- Open up the PowerShell Management Console and enter "Get-SPWebApplication". This will list all your web applications on the farm.
- To apply it to all My Sites (each site is a site collection of its own) run this script below.
1: $webapps = Get-SPWebApplication;
2:
3: $webapp = $webapps[4];
4:
5: foreach ($site in $webapp.Sites) {
6: Set-SPSite -Identity $site.url -QuotaTemplate "Your Quota Template"
7: }
The first line gets all the web applications on the server. In our case, the forth one is the mysite web app (yours will probably be a different number). Just run Get-SPWebApplication from the console to figure out which one to use. You could get fancy and pipe the name to find it but I'm too lazy for that.
Then we loop through all the sites on the list using the $site.url property and pass it to the Set-SPSite cmdlet and specify the name of the our custom QuotaTemplate.
Easy. Now all users are updated with the new quota template.
-
Imperative vs. LINQ Performance on WP7
Jesse Liberty had a nice post presenting the concepts around imperative, LINQ and fluent programming to populate a listbox. Check out the post as it’s a great example of some foundational things every .NET programmer should know.
I was more interested in what the IL code that would be generated from imperative vs. LINQ was like and what the performance numbers are and how they differ.
The code at the instruction level is interesting but not surprising. The imperative example with it’s creating lists and loops weighs in at about 60 instructions.
1: .method private hidebysig instance void ImperativeMethod() cil managed
2: {
3: .maxstack 3
4: .locals init (
5: [0] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> someData,
6: [1] class [mscorlib]System.Collections.Generic.List`1<int32> inLoop,
7: [2] int32 n,
8: [3] class [mscorlib]System.Collections.Generic.IEnumerator`1<int32> CS$5$0000,
9: [4] bool CS$4$0001)
10: L_0000: nop
11: L_0001: ldc.i4.1
12: L_0002: ldc.i4.s 50
13: L_0004: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
14: L_0009: stloc.0
15: L_000a: newobj instance void [mscorlib]System.Collections.Generic.List`1<int32>::.ctor()
16: L_000f: stloc.1
17: L_0010: nop
18: L_0011: ldloc.0
19: L_0012: callvirt instance class [mscorlib]System.Collections.Generic.IEnumerator`1<!0> [mscorlib]System.Collections.Generic.IEnumerable`1<int32>::GetEnumerator()
20: L_0017: stloc.3
21: L_0018: br.s L_003a
22: L_001a: ldloc.3
23: L_001b: callvirt instance !0 [mscorlib]System.Collections.Generic.IEnumerator`1<int32>::get_Current()
24: L_0020: stloc.2
25: L_0021: nop
26: L_0022: ldloc.2
27: L_0023: ldc.i4.5
28: L_0024: cgt
29: L_0026: ldc.i4.0
30: L_0027: ceq
31: L_0029: stloc.s CS$4$0001
32: L_002b: ldloc.s CS$4$0001
33: L_002d: brtrue.s L_0039
34: L_002f: ldloc.1
35: L_0030: ldloc.2
36: L_0031: ldloc.2
37: L_0032: mul
38: L_0033: callvirt instance void [mscorlib]System.Collections.Generic.List`1<int32>::Add(!0)
39: L_0038: nop
40: L_0039: nop
41: L_003a: ldloc.3
42: L_003b: callvirt instance bool [mscorlib]System.Collections.IEnumerator::MoveNext()
43: L_0040: stloc.s CS$4$0001
44: L_0042: ldloc.s CS$4$0001
45: L_0044: brtrue.s L_001a
46: L_0046: leave.s L_005a
47: L_0048: ldloc.3
48: L_0049: ldnull
49: L_004a: ceq
50: L_004c: stloc.s CS$4$0001
51: L_004e: ldloc.s CS$4$0001
52: L_0050: brtrue.s L_0059
53: L_0052: ldloc.3
54: L_0053: callvirt instance void [mscorlib]System.IDisposable::Dispose()
55: L_0058: nop
56: L_0059: endfinally
57: L_005a: nop
58: L_005b: ldarg.0
59: L_005c: ldfld class [System.Windows]System.Windows.Controls.ListBox PerfTest.MainPage::LB1
60: L_0061: ldloc.1
61: L_0062: callvirt instance void [System.Windows]System.Windows.Controls.ItemsControl::set_ItemsSource(class [mscorlib]System.Collections.IEnumerable)
62: L_0067: nop
63: L_0068: ret
64: .try L_0018 to L_0048 finally handler L_0048 to L_005a
65: }
66:
67:
Compare that to the IL generated for the LINQ version which has about half of the instructions and just gets the job done, no fluff.
1: .method private hidebysig instance void LINQMethod() cil managed
2: {
3: .maxstack 4
4: .locals init (
5: [0] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> someData,
6: [1] class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> queryResult)
7: L_0000: nop
8: L_0001: ldc.i4.1
9: L_0002: ldc.i4.s 50
10: L_0004: call class [mscorlib]System.Collections.Generic.IEnumerable`1<int32> [System.Core]System.Linq.Enumerable::Range(int32, int32)
11: L_0009: stloc.0
12: L_000a: ldloc.0
13: L_000b: ldsfld class [System.Core]System.Func`2<int32, bool> PerfTest.MainPage::CS$<>9__CachedAnonymousMethodDelegate6
14: L_0010: brtrue.s L_0025
15: L_0012: ldnull
16: L_0013: ldftn bool PerfTest.MainPage::<LINQProgramming>b__4(int32)
17: L_0019: newobj instance void [System.Core]System.Func`2<int32, bool>::.ctor(object, native int)
18: L_001e: stsfld class [System.Core]System.Func`2<int32, bool> PerfTest.MainPage::CS$<>9__CachedAnonymousMethodDelegate6
19: L_0023: br.s L_0025
20: L_0025: ldsfld class [System.Core]System.Func`2<int32, bool> PerfTest.MainPage::CS$<>9__CachedAnonymousMethodDelegate6
21: L_002a: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0> [System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Core]System.Func`2<!!0, bool>)
22: L_002f: ldsfld class [System.Core]System.Func`2<int32, int32> PerfTest.MainPage::CS$<>9__CachedAnonymousMethodDelegate7
23: L_0034: brtrue.s L_0049
24: L_0036: ldnull
25: L_0037: ldftn int32 PerfTest.MainPage::<LINQProgramming>b__5(int32)
26: L_003d: newobj instance void [System.Core]System.Func`2<int32, int32>::.ctor(object, native int)
27: L_0042: stsfld class [System.Core]System.Func`2<int32, int32> PerfTest.MainPage::CS$<>9__CachedAnonymousMethodDelegate7
28: L_0047: br.s L_0049
29: L_0049: ldsfld class [System.Core]System.Func`2<int32, int32> PerfTest.MainPage::CS$<>9__CachedAnonymousMethodDelegate7
30: L_004e: call class [mscorlib]System.Collections.Generic.IEnumerable`1<!!1> [System.Core]System.Linq.Enumerable::Select<int32, int32>(class [mscorlib]System.Collections.Generic.IEnumerable`1<!!0>, class [System.Core]System.Func`2<!!0, !!1>)
31: L_0053: stloc.1
32: L_0054: ldarg.0
33: L_0055: ldfld class [System.Windows]System.Windows.Controls.ListBox PerfTest.MainPage::LB2
34: L_005a: ldloc.1
35: L_005b: callvirt instance void [System.Windows]System.Windows.Controls.ItemsControl::set_ItemsSource(class [mscorlib]System.Collections.IEnumerable)
36: L_0060: nop
37: L_0061: ret
38: }
Again, not surprising here but a good indicator that you should consider using LINQ where possible. In fact if you have ReSharper installed you’ll see a squiggly (technical term) in the imperative code that says “Hey Dude, I can convert this to LINQ if you want to be c00L!” (or something like that, it’s the 2010 geek version of Clippy).
What about the fluent version? As Jon correctly pointed out in the comments, when you compare the IL for the LINQ code and the IL for the fluent code it’s the same. LINQ and the fluent interface are just syntactical sugar so you decide what you’re most comfortable with. At the end of the day they’re both the same.
Now onto the numbers. Again I expected the imperative version to be better performing than the LINQ version (before I saw the IL that was generated). Call it womanly instinct. A gut feel. Whatever. Some of the numbers are interesting though.
For Jesse’s example of 50 items, the numbers were interesting. The imperative sample clocked in at 7ms while the LINQ version completed in 4. As the number of items went up, the elapsed time didn’t necessarily climb exponentially. At 500 items they were pretty much the same and the results were similar up to about 50,000 items. After that I tried 500,000 items where the gap widened but not by much (2.2 seconds for imperative, 2.3 for LINQ). It wasn’t until I tried 5,000,000 items where things were noticeable. Imperative filled the list in 20 seconds while LINQ took 8 seconds longer (although personally I wouldn’t suggest you put 5 million items in a list unless you want your users showing up at your door with torches and pitchforks).
Here’s the table with the full results.
Method/Items </STRONG><TD vAlign="top" width="71"><STRONG>50</STRONG></TD><STRONG> </STRONG><TD vAlign="top" width="71"><STRONG>500</STRONG></TD><STRONG> </STRONG><TD vAlign="top" width="71"><STRONG>5,000</STRONG></TD><STRONG> </STRONG><TD vAlign="top" width="71"><STRONG>50,000</STRONG></TD><STRONG> </STRONG><TD vAlign="top" width="71"><STRONG>500,000</STRONG></TD><STRONG> </STRONG><TD vAlign="top" width="71"><STRONG>5,000,000</STRONG></TD> </TR> <TR> <TD vAlign="top" width="71">Imperative</TD> <TD vAlign="top" width="71">7ms</TD> <TD vAlign="top" width="71">7ms</TD> <TD vAlign="top" width="71">38ms</TD> <TD vAlign="top" width="71">223ms</TD> <TD vAlign="top" width="71">2230ms</TD> <TD vAlign="top" width="71">20974ms</TD> </TR> <TR> <TD vAlign="top" width="71">LINQ/Fluent</TD> <TD vAlign="top" width="71">4ms</TD> <TD vAlign="top" width="71">6ms</TD> <TD vAlign="top" width="71">41ms</TD> <TD vAlign="top" width="71">240ms</TD> <TD vAlign="top" width="71">2310ms</TD> <TD vAlign="top" width="71">28731ms</TD> </TR>
Like I said, at the end of the day it’s not a huge difference and you really don’t want your users waiting around for 30 seconds on a mobile device filling lists. In fact if Windows Phone 7 detects you’re taking more than 10 seconds to do any one thing, it considers the app hung and shuts it down. The results here are for Windows Phone 7 but frankly they're the same for desktop and web apps so feel free to apply it generally.
From a programming perspective, choose what you like. Some LINQ statements can get pretty hairy so I usually fall back with my simple mind and write it imperatively. If you really want to impress your friends, write it old school then let ReSharper do the hard work for!
Happy programming!
-
Generic Pop and Push for List<T>
Here's a little snippet I use to extend a generic List class to have similar capabilites to the Stack class.
The Stack<T> class is great but it lives in its own world under System.Object. Wouldn't it be nice to have a List<T> that could do the same? Here's the code:
1: public static class ExtensionMethods
2: {
3: public static T Pop<T>(this List<T> theList)
4: {
5: var local = theList[theList.Count - 1];
6: theList.RemoveAt(theList.Count - 1);
7: return local;
8: }
9:
10: public static void Push<T>(this List<T> theList, T item)
11: {
12: theList.Add(item);
13: }
14: }
It's a simple extension but I've found it useful, hopefully you will too! Enjoy.
-
WP7 “Phantom Data” Source Possibly Revealed?
Recently there’s been rumours floating around regarding “phantom” Windows Phone 7 data being magically sent and received on the latest WP7 phones. The news has mostly been floating around twitter so I didn’t pay it much attention. The BBC Technology News picked it up so I thought I would look more into it myself seeing that we have WP7 phones and maybe there was some truth to all this (and more importantly what was the cause).
Full disclosure. I don’t have a lot of data points around this. This is from looking at a few phone logs, changing the configuration and looking back again after the change. I haven’t done a clean baseline test nor have I done testing with hundreds of phones. I leave the experience up to the reader to decide.
So I went spelunking into the phone logs to see what was up. Most providers will show you data usage, at least on a daily basis. I lucked out with the provider and plan in that they provide hourly breakdowns. Here’s a snapshot from my usage throughout one night.
Timestamp Data Usage 12:38:30 AM 2098 Kilobytes 1:30:30 AM 2 Kilobytes 2:38:30 AM 7118 Kilobytes 3:38:30 AM 6622 Kilobytes 4:38:30 AM 76 Kilobytes 5:38:30 AM 29 Kilobytes 6:38:30 AM 19 Kilobytes 7:38:30 AM 20 Kilobytes So a few observations from this data:
- Data seems to be collected on a regular basis. Looking at some other people phone logs, the times vary but it’s always hourly.
- There’s not a tremendous amount of data here (about 16 megabytes) but it seems like a lot for 7 hours
- The phone was connected to my home Wifi during this period
- Nothing was running and the phone was in a locked state
Like I said, not a lot of data but it adds up. 16MB for 7 hours = about 50MB in a 24 hour period. That’s just plain old data being collected (somewhere, somehow) and not actual usage (Marketplace, Email, Browsing, etc.). Besides, when connected to a WiFi network you shouldn’t be charged data usage from your phone company (in theory, YMMV).
After reviewing the logs I made a theory that the only thing that could possibly be sending data is the Feedback feature. With no other apps running under lock, what else could it be?
In Windows 7 under your Settings the last option is Feedback. This sends feedback to Microsoft to “help improve Windows Phone”. On this page you have three options:
- Send feedback and use my cellular data connection
- Send feedback and (presumably) use my WiFi connection
- Don’t send feedback
Knowing what I know about Microsoft, they do use the feedback data. For example some of the placement and inclusion of features in Office 2007 was based on that Feedback data that Office sends (assuming you had opted in).
However in the Privacy Statement (it’s long but a good read at least once in your life), the Phone manual, and every other source I could look at there is no information about how much data it’s planning to send, just that it’s sending some data and that “some data charges with your carrier may apply”.
Looking back at the logs, I have to wonder. 6MB at 3:30 and *then* 7MB the next hour. That’s a lot of information. And it adds up. 50MB in a 24 hour period X 30 days puts most people over a normal 1GB plan. And frankly why am I paying for a data plan only to have 80% of it chewed up by Microsoft, with no real benefit to me. If they included porn in the 50mb daily transfer I’d be okay with this, but I don’t see any new movies on my phone.
So I turned it off. Set Feedback to disabled and wait.
I waited. And waited. And generally didn’t use the phone if I could.
The next day I went back to look at the data usage logs from the time period after turning the feedback mechanism off. Here are the results.
Timestamp Data Usage 1:19:48 PM 0 Kilobytes 2:19:48 PM 0 Kilobytes 3:19:48 PM 0 Kilobytes 4:19:48 PM 678 Kilobytes (took a phone call) 5:19:48 PM 82 Kilobytes 6:19:48 PM 88 Kilobytes 7:20:30 PM 86 Kilobytes (guess they changed their reporting time) 8:20:30 PM 86 Kilobytes 9:20:30 PM 66 Kilobytes 10:20:30 PM 67 Kilobytes 11:20:30 PM 49 Kilobytes 12:20:30 AM 32 Kilobytes 1:20:30 AM 38 Kilobytes 2:20:31 AM 18 Kilobytes 3:20:31 AM 27 Kilobytes 4:20:31 AM 86 Kilobytes 5:20:31 AM 53 Kilobytes 6:20:31 AM 22 Kilobytes 7:22:15 AM 30 Kilobytes (another reporting time change) 8:22:15 AM 29 Kilobytes 9:22:15 AM 74 Kilobytes 10:22:15 AM 154 Kilobytes (phone call) 11:22:15 AM 12 Kilobytes 12:13:27 PM 49 Kilobytes 1:13:27 PM 197 Kilobytes (phone call) Quite a *drastic* change from what Feedback was turned on. I mean for a 24 hour period (sans 3 phone calls) I consumed about 1MB. Still quite a bit of transfer going on but at least it amounts to 30MB per month, not 30MB per day!
Like I said this observation is neither scientific or conclusive. You decide what to do but frankly until Microsoft makes this data transfer exempt from your data plan (like that will happen) I would just turn Feedback off. YMMV.
-
Do’s and Don’ts Building SharePoint Applications
SharePoint is a great platform for building quick LOB applications. Simple things from employee time trackers to server and software inventory to full blown Help Desks can be crafted up using SharePoint from just customizing Lists. No programming necessary. However there are a few tricks I’ve painfully learned over the years that you can use for your own solutions.
DO
What’s In A Name?
When you create a new list, column, or view you’ll commonly name it something like “Expense Reports”. However this has the ugly effect of creating a url to the list as “Expense%20Reports”. Or worse, an internal field name of “Expense_x0x0020_Reports” which is not only cryptic but hard to remember when you’re trying to find the column by internal name.
While “Expense Reports 2011” is user friendly, “ExpenseReports2011” is not (unless you’re a programmer). So that’s not the solution. Well, not entirely.
Instead when you create your column or list or view use the scrunched up name (I can’t think of the technical term for it right now) of “ExpenseReports2011”, “WomenAtTheOfficeThatAreMen” or “KoalaMeatIsGoodWhenBroiled”. After you’ve created it, go back and change the name to the more friendly “Silly Expense Reports That Nobody Reads”. The original internal name will be the url and code friendly one without spaces while the one used on data entry forms and view headers will be the human version.
Smart Columns
When building a view include columns that make sense. By default when you add a column the “Add to default view” is checked. Resist the urge to be lazy and leave it checked. Uncheck that puppy and decide consciously what columns should be included in the view.
Pick columns that make sense to what the user is trying to do. This means you have to talk to the user. Yes, I know. That can be trying at times and even painful. Go ahead, talk to them. You might learn something.
Find out what’s important to them and why. If they’re doing something repetitively as part of their job, try to make their life easier by including what’s most important to them. Do they really need to see the Created *and* Modified date of a document or do they just need the title and author? You’ll only find out after talking to them (or getting them drunk in a bar and leaving them in the back alley handcuffed to a garbage bin, don’t ask).
Gotta Keep it Separated
Hey, views are there for a reason. Use them. While “All Items” is a fine way to present a list of well, all items, it’s hardly sufficient to present a list of servers built before the Y2K bug hit. You’ll be scrolling the list for hours finally arriving at Page 387 of 12,591 and cursing that SharePoint guy for convincing you that putting your hardware into a list would be of any use to anyone.
Next to collecting the data, presenting it is just as important. Views are often overlooked and many times ignored or misused. They’re the way you can slice and dice the data up so that you’re not trying to consume 3,000 years of human evolution on a single web page.
Remember views can be filtered so feel free to create a view for each status or one for each operating system or one for each species of Information Worker you might be putting in that list or document library. Not only will it reduce the number of items someone sees at one time, it’ll also make the information that much more relevant.
Also remember that each view is a separate page. Use it in navigation by creating a menu on the Quick Launch to each view. The discoverability of the Views menu isn’t overly obvious and if you violate the rule of columns (see Horizontally Scrolling below) the view menu doesn’t even show up until you shuffle the scroll bar to the left. Navigation links, big giant buttons, a screaming flashing “CLICK ME NOW” will help your users find their way.
Sort It!
Views are great so we’re building nice, rich views for the user. Awesomesauce. However sort is not very discoverable by the user. For example when you’re looking at a view how do you know if it’s ascending or descending and what is it sorted on. Maybe it’s sorted using two fields so what’s that all about?
Help your users by letting them know the information they’re looking at is sorted. Maybe you name the view something appropriate like “Bogus Expense Claims Sorted By Deadbeats”. If you use the naming strategy just make sure you keep the name consistent with the description. In the previous example their better be a Deadbeat column so I can see the sort in action. Having a “Loser” column, while equally correct, is a little obtuse to the average Information Worker. Remember, they usually don’t use synonyms and even if they knew how to, it’s not immediately obvious to them that’s what you’re trying to convey.
Another option is to simply drop a Content Editor Web Part above the list and explain exactly the view they’re looking at. Each view is it’s own page so one CEWP won’t be used across the board. Be descriptive in what the user is seeing but try to keep it brief. Dumping the first chapter of I, Claudius might be informative to the data but can gobble up screen real estate and miss the point of having the list.
DO NOT
Useless Attachments
The attachments column is, in a word, useless. For the most part. Sure it indicates there’s an attachment on the list item but in the grand scheme of things that’s not overly informative. Maybe it is and by all means, if it makes sense to you include it. Colour it. Make it shine and stand like the Return of Clippy on every SharePoint list.
Without it being functional it can be boring. EndUserSharePoint.com has an article to make the son of Clippy that much more useful so feel free to head over and check out this blog post by Paul Grenier on the task (Warning code ahead! Danger Will Robinson!)
In any case, I would suggest you remove it from your views. Again if it’s important then include it but consider the jQuery solution above to make it functional. It’s added by default to views and one of things that people forget to clean up.
Horizontal Scrolling
Screen real estate is premium so building a list that contains 8,000 columns and stretches horizontally across 15 screens probably isn’t the most user friendly experience. Most users can’t figure out how to scroll vertically let alone horizontally so don’t make it even that more confusing for them. Take the Steve Krug approach in your view designs and try not to make the user think.
Again views are your friend. Consider splitting up the data into views where one view contains 10 columns and other view contains the other 10. Okay, maybe your information doesn’t work that way but humans can only process 7 pieces of data at a time, 10 at most (then their heads explode and you don’t want to clean that mess up, especially on a Friday night before the big dance).
It drives me batshit crazy when I see a view with 80 columns of data. I often ask the user “So what do you do with all this information”. The response is usually “With this data [the first 10 columns] I decide if I’m going to fire everyone, and with this data [the next 10 columns] I decide if I’m going to set the building on fire and collect the insurance”. It’s at that point I show them how to create two new views “People Who Are About To Get The Axe” and “Beach Time For The Executives”. Again, talk to your users and try to reason with them on cutting down the number of columns they see at once.
Vertical Scrolling
Another big faux pas I find is the use of multi-line comment fields in views. It’s not so bad when you have a statement like this in your view:
“I really like, oh my god, thought I was going to scream when I saw this turtle then I decided what I was going to have for dinner and frankly I hate having to work late so when I was talking to the customer I thought, oh my god, what if the customer has turtles and then it appeared to me that I really was hungry so I'm going to have lunch now.”
It’s fine if that’s the only column along with two or three others, but once you slap those 20 columns of data into the list, the comment field wraps and forms a new multi-page novel that takes up your entire screen.
Do everyone a favour and just avoid adding the column to views. Train the user to just click through to the item if they need to see the contents.
Duplicate Information
Duplication is never good. Views and great as you can group data together. For example create a view of project status reports grouped by author. Then you can see what project manager is being a dip and not submitting their report. However if you group by author do you really need the Created By field as well in the view? Or if the view is grouped by Project then Author do you need both.
Horizontal real estate is always at a premium so try not to clutter up the view with duplicate data like this. Oh yeah, if you’re scratching your head saying “But Bil, if I don’t include the Project name in the view and I have a lot of items then how do I know which one I’m looking at”. That’s a hint that your grouping is too vague or you have too much data in the view based on that criteria. Filter it down a notch, create some views, and try to keep the group down to a single screen where you can see the group header at the top of the page. Again it’s just managing the information you have.
Redundant, See Redundant
This partially relates to duplicate information and smart columns but basically remember to not include the obvious in a view. Remember, don’t make me think. If you’ve gone to the trouble (and it was a lot of trouble wasn’t it?) to create separate views of your data by creating a “September Zombie Brain Sales”, “October Zombie Brain Sales”, etc. then please for the love of all that is holy do not include the Month and Product columns in your view. Similarly if you create a “My” view of anything (“My Favourite Brands of Spandex”, “My Co-Workers I Find The Urge To Disinfect”) then again, do not include the owner or author field (or whatever field you use to identify “My”). That’s just silly.
Hope that helps! Happy customizing!
-
500 Metro Style WP7 Icons
I was inspired by The Noun Project, a project that offers up “Metro-style” icons in SVG format. The project is licensed under a public domain license and while it’s a great project, all of the content is in SVG format. Jon Galloway has a great post (from 2007) talking about the differences between SVG and XAML so I highly recommend that for some background. I thought it would be helpful to the WPF/Windows Phone 7/Silverlight community to provide the content in alternative formats for use in your applications.
The Goods
I’ve put together a package of the 500 icons (502 actually) in PNG, XAML and the original SVG format along with a couple of sample projects so you can see them in action.
There’s a WPF desktop app:
And a Windows Phone 7 app:
Building It
To get all the content first I wrote up a quick program to suck the original SVG files. Luckily they’re all in a common path just named 1.SVG, 2.SVG, and so on. Easy sleazy to grab the contents. Once I had 500 SVG files I used the latest copy of XamlTune, an open source CodePlex project that has a command line conversion tool to convert the directory of SVG files into XAML (the tool also created a PNG file of each SVG so that’s just icing on the cake).
Conversions
The conversion from SVG to XAML isn’t 100%. While you can just drop the content into a WPF app, it doesn’t work that way for WP7. There are just some small adjustments I made to each format so you’ll have to do the same. Follow the information below or refer to the sample applications.
As a sample, here’s an icon we want to use:
Here’s the original SVG file:
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="100px" height="94.616px" viewBox="0 0 100 94.616" enable-background="new 0 0 100 94.616" xml:space="preserve"> <path d="M25.076,15.639c4.324,0.009,7.824-3.488,7.82-7.82C32.9,3.512,29.4,0.012,25.076,0c-4.313,0.012-7.814,3.512-7.821,7.819 C17.262,12.15,20.763,15.648,25.076,15.639L25.076,15.639z"/> <path d="M4.593,43.388h6.861l4.137-15.135h1.716L13.22,43.388h24.318l-4.389-15.135h1.817l2.32,7.415 c1.08,3.131,3.852,3.851,6.003,1.162l8.375-10.142c2.651-3.42-2.104-7.021-4.844-4.035l-4.993,5.952 c0.007,0.095-0.96-3.278-0.96-3.278c-1.135-3.978-4.918-7.903-10.595-7.922H19.576c-5.071,0.019-9.043,4.434-9.888,7.214 L4.593,43.388L4.593,43.388z"/> <polygon points="56.206,22.753 56.206,7.163 49.192,7.163 49.192,22.753 56.206,22.753 "/> <path d="M79.87,15.738c4.332-0.014,7.831-3.516,7.82-7.82c0.011-4.332-3.488-7.833-7.82-7.82c-4.306-0.013-7.806,3.488-7.821,7.82 C72.064,12.222,75.564,15.725,79.87,15.738L79.87,15.738z"/> <path d="M89.759,89.556v-43.19h5.751V22.804c0.007-3.079-2.757-5.448-6.71-5.449H70.436c-3.65,0.001-4.539,1.186-5.551,2.168 L49.597,37.889c-3.098,3.848,2.428,8.333,5.55,4.743L69.88,25.226v64.43c-0.019,6.475,9.06,6.686,9.081,0.201v-36.58h1.765v36.379 C80.748,96.109,89.772,96.13,89.759,89.556L89.759,89.556z"/> <polygon points="100,54.035 100,45.155 0,45.155 0,54.035 100,54.035 "/> </svg>
Here’s the XAML that XamlTune created. It can be used in any WPF app without any changes:
<Canvas Name="Layer_1" Width="100" Height="94.616" ClipToBounds="True" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <Path Fill="#FF000000"> <Path.Data> <PathGeometry FillRule="Nonzero" Figures="M25.076,15.639C29.4,15.648 32.9,12.151 32.896,7.819 32.9,3.512 29.4,0.012 25.076,0 20.763,0.012 17.262,3.512 17.255,7.819 17.262,12.15 20.763,15.648 25.076,15.639L25.076,15.639z" /> </Path.Data> </Path> <Path Fill="#FF000000"> <Path.Data> <PathGeometry FillRule="Nonzero" Figures="M4.593,43.388L11.454,43.388 15.591,28.253 17.307,28.253 13.22,43.388 37.538,43.388 33.149,28.253 34.966,28.253 37.286,35.668C38.366,38.799,41.138,39.519,43.289,36.83L51.664,26.688C54.315,23.268,49.56,19.667,46.82,22.653L41.827,28.605C41.834,28.7 40.867,25.327 40.867,25.327 39.732,21.349 35.949,17.424 30.272,17.405L19.576,17.405C14.505,17.424,10.533,21.839,9.688,24.619L4.593,43.388 4.593,43.388z" /> </Path.Data> </Path> <Path Fill="#FF000000"> <Path.Data> <PathGeometry FillRule="Nonzero" Figures="M56.206,22.753L56.206,7.163 49.192,7.163 49.192,22.753 56.206,22.753z" /> </Path.Data> </Path> <Path Fill="#FF000000"> <Path.Data> <PathGeometry FillRule="Nonzero" Figures="M79.87,15.738C84.202,15.724 87.701,12.222 87.69,7.918 87.701,3.586 84.202,0.0849999999999991 79.87,0.097999999999999 75.564,0.084999999999999 72.064,3.586 72.049,7.918 72.064,12.222 75.564,15.725 79.87,15.738L79.87,15.738z" /> </Path.Data> </Path> <Path Fill="#FF000000"> <Path.Data> <PathGeometry FillRule="Nonzero" Figures="M89.759,89.556L89.759,46.366 95.51,46.366 95.51,22.804C95.517,19.725,92.753,17.356,88.8,17.355L70.436,17.355C66.786,17.356,65.897,18.541,64.885,19.523L49.597,37.889C46.499,41.737,52.025,46.222,55.147,42.632L69.88,25.226 69.88,89.656C69.861,96.131,78.94,96.342,78.961,89.857L78.961,53.277 80.726,53.277 80.726,89.656C80.748,96.109,89.772,96.13,89.759,89.556L89.759,89.556z" /> </Path.Data> </Path> <Path Fill="#FF000000"> <Path.Data> <PathGeometry FillRule="Nonzero" Figures="M100,54.035L100,45.155 0,45.155 0,54.035 100,54.035z" /> </Path.Data> </Path> </Canvas>
The XAML works AS-IS in a WPF application but there are some changes I did to get it to work in a WP7 app. Here’s the modified XAML in a WP7 application:
<Canvas Grid.Row="0" Grid.Column="0" Name="Icon_1" Width="100" Height="94.616"> <Path Fill="#FF000000" Data="M25.076,15.639C29.4,15.648 32.9,12.151 32.896,7.819 32.9,3.512 29.4,0.012 25.076,0 20.763,0.012 17.262,3.512 17.255,7.819 17.262,12.15 20.763,15.648 25.076,15.639L25.076,15.639z"> </Path> <Path Fill="#FF000000" Data="M4.593,43.388L11.454,43.388 15.591,28.253 17.307,28.253 13.22,43.388 37.538,43.388 33.149,28.253 34.966,28.253 37.286,35.668C38.366,38.799,41.138,39.519,43.289,36.83L51.664,26.688C54.315,23.268,49.56,19.667,46.82,22.653L41.827,28.605C41.834,28.7 40.867,25.327 40.867,25.327 39.732,21.349 35.949,17.424 30.272,17.405L19.576,17.405C14.505,17.424,10.533,21.839,9.688,24.619L4.593,43.388 4.593,43.388z"> </Path> <Path Fill="#FF000000" Data="M56.206,22.753L56.206,7.163 49.192,7.163 49.192,22.753 56.206,22.753z"> </Path> <Path Fill="#FF000000" Data="M79.87,15.738C84.202,15.724 87.701,12.222 87.69,7.918 87.701,3.586 84.202,0.0849999999999991 79.87,0.097999999999999 75.564,0.084999999999999 72.064,3.586 72.049,7.918 72.064,12.222 75.564,15.725 79.87,15.738L79.87,15.738z"> </Path> <Path Fill="#FF000000" Data="M89.759,89.556L89.759,46.366 95.51,46.366 95.51,22.804C95.517,19.725,92.753,17.356,88.8,17.355L70.436,17.355C66.786,17.356,65.897,18.541,64.885,19.523L49.597,37.889C46.499,41.737,52.025,46.222,55.147,42.632L69.88,25.226 69.88,89.656C69.861,96.131,78.94,96.342,78.961,89.857L78.961,53.277 80.726,53.277 80.726,89.656C80.748,96.109,89.772,96.13,89.759,89.556L89.759,89.556z"> </Path> <Path Fill="#FF000000" Data="M100,54.035L100,45.155 0,45.155 0,54.035 100,54.035z"> </Path> </Canvas>
All I did was take the data portion and put it directly into a Data attribute on the Path. Note that while it does show up in the app (on the emulator or device) it wouldn’t show up in Visual Studio for me. Maybe some XAML guru out there can tell me why. You can just as easily use the PNG files in WP7 but if you want the crispness of vector graphics, go for the XAML version.
Of course with XamlTune being open source you could always modify the output of that program to cater it to your app. If you do make a change that’s worthy please consider submitting a patch to the project so everyone can benefit.
Hope this helps and happy programming!
Resources and Links
- Sample Project and Icons
- XamlTune an open source project to convert SVG to XAML
- The Noun Project source of the original files
- Jon Galloways post on SVG and XAML
- StackOverflow question on converting SVG to XAML