Jaime Rodriguez On Windows Store apps, Windows Phone, HTML and XAML
First of all, best wishes for your 2009. I am a bit late to with the good wishes, and even later with posts that happened during the holiday break, but I still wanted to make sure you did not miss these:
That gets me all caught up.. Apologies if it is all dupes..
If you ask any of the XAML practitioners about the XAML best practices on organizing a project, resources, etc.. they will all give you a single answer “it depends; every project is different”.. If you then ask them for details, you will get a slightly different answer from each expert. In a brief attempt to aggregate the recurrent ‘good practices’, I recently interviewed a few folks about their preferences.. The first interview was IdentityMine's Jonathan Russ, Nathan Dunlap and Jared Potter. These folks are some of the most experienced XAMLites I know.. Jonathan & Nathan date back to the early Avalon days.. Check out the video using Adam's play-by-play timeline by clicking on the image or the 3 wise guys.. [I will try to figure how to embed videos in the blog by the next part in this series] Huge thanks to Jonathan, Nathan, Jared and the IdentityMine team for taking the time for the interview and to Adam for doing his magic with the video.
DataContext is a very handy inherited property on any WPF application.. Most of the time, I set DataContext near the root on the [logical] tree, and let the inherited DataContext do its magic to bind the rest of the scene. I recently tried to bind a DataGridColumn to its inherited DataContext (via its datagrid container) and got a very surprising answer on the output trace window:
“System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element…”
What is happening here? The Columns collection is just a property in the Datagrid; this collection is not in the logical (or visual) tree, therefore the DataContext is not being inherited, which leads to there being nothing to bind to.
I needed the functionality so I had to create a workaround.. With out much thought I decided to:
To get it done, I did not inherit from DataGrid and create a new class.. Instead i used the richness of WPF’s property system to pull a 1,2 punch:
Code looks like this:
The OnDataContextChanged callback simply forwards the DataContext from DataGrid to its columns:
public static void OnDataContextChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e) { DataGrid grid = d as DataGrid ; if ( grid != null ) { foreach ( DataGridColumn col in grid.Columns ) { col.SetValue ( FrameworkElement.DataContextProperty, e.NewValue ); } } }
That is it. Now we are ready to databind to DataGridColumns. <dg:DataGridTextColumn Binding="{BindingA}" Visibility="{Binding ElementName=ShowA,Path=IsChecked , Converter={StaticResource BoolToVisConverter}}" />
You can download source code for a small sample from here.
A few more thoughts on DataGridColumn not being in the tree ..
Happy datagrid coding.. Again, there is source here.
The Silverlight toolkit CTP released at PDC includes some new charting functionality (Column, Bar, Pie, Line, and Scatter). The long-term plan is for all of these controls to be ported to WPF; but inspired by Rudi’s work on porting the themes, I peeked into the task to see if the Microsoft continuum would hold for the controls too.
The results were darn good, I had the charts project compiled in WPF in ~20 mins; and after that, I only had to make a few run-time tweaks to get the project running… ( look for the #WPF pre-processor in the code.. there is only a handful).
Since the charting library is an early "preview" quality, I will probably not do a full port or any testing, but in case some one wants to carry it further, the source code is here..
Screenshot follows… These are the same charts as in the SL Toolkit sample but running in a WPF app (named Window1 for Rob)..
Today, Rudi Grobler released a “WPF Themes Pack” on codeplex. The project includes eight XAML themes for WPF applications… Read his announcement here. WPF Themes Codeplex project is here.
I like his ‘Expression Dark’ theme.. It feels safe (and familiar) and I like the DavesGlossy theme too (screenshots below).
Since some of the themes also ship with silverlight, this could be an easy way to create a consistent UI for an application that has a web client (Silverlight) and a destktop client (WPF).
Thanks Rudi!!
One of my favorite WPF apps is the NY times reader.. but a while ago they started charging a subscription fee and {since I am a cheap skate, and a very seldom commuter} I stopped using it..
Now they are back with a Free Edition.. It includes Top Stories, Select Articles from Sunday Magazine, and Weekly crossword puzzle.. this is plenty for my 35 min once a week commute. It will be good to keep up w/ the news to see who wins the US Presidential election (just kidding, I am not that out of touch –with the news at least) ..
Install Times Reader Free Edition from here, and if you commute more often than I do, feel free to subscribe to full edition..
Today, on codeplex we released updates to the WPF Toolkit. This release includes:
We also release the Preview version of Ribbon. Install instructions here.
PDC has been taking most of my time lately so I don’t [yet] have a great tutorial or a geek-out post :(… I will come back to that..
We just published a codeplex project with source for > 25 Pixel Shader effects and ~35 Transition effects..
This video demonstrating the effects and transitions is a must watch. it is much better than the descriptions below.. [but for any one with less bandwidth I still tried]..
The scoop on the library. Adam recorded a video with David Teitlebaum introducing the library and sharing credit with Troy Jefferson, the intern that packaged the effects... Thanks Troy!! We are hoping others contribute; there is already plenty of other WPF effects out there.. A few resources to get you going with PixelShaders (for WPF) effects:
Have fun! Please share feedback via codeplex.. and if you like the library blog it so others can find it.. imho the transitions are pretty neat!
Earlier in the year, you might have seen screen shots of Lawson’s Smart Office application. Now, thanks to Adam Kinney and Matt Allbee (from Lawson), you can actually see it live in this 9 min interview/walk through! I recommend the high quality video for the interactivity, but for those not patient enough, I have sprinkled lots of screen shots below with a brief (not all-inclusive) summary around the usage of WPF.
Smart Office is a front-end to Lawson's suite of applications -which includes Enterprise Resource Planning (ERP), Supply Chain Management (SCM), Enterprise Performance Management (EPM), and Custome Resource Management (CRM). The best description I have heard for Smart Office is Matt calling it an "information workspace". A typical user can spend all day inside the app, as such it is a full-screen applications that simulates a shell -they call it a "Canvas"-. It has everything inside the app, window management, quick task switching, sidebar, drag-drop, shell and office integration, and of course lots of screens or apps to interact with the Lawson back-end. Smart Office aims to make an organization's people more effective, their processes more efficient, and the end-users happier. Smart Office focuses on the user's needs, and focuses on simplicity and meaningful collaboration to improve user and group productivity. Here is how they 'get it done':
Smart Office has it all: 3D for navigation and for charting.
Floating,translucent windows that blend well and allow users to take quick actions with out concealing the other user interface.
Video for user tutorials and walk-throughs.
Flow Documents are used for help files, to create an adaptive, high-quality reading experience. Uses VisualBrush(es) to implement previews in their taskbar -similar to Windows Vista's Alt-Tab-. It reuses this technique for data entry to preview forms before you open them. .
Figure 1. You can see the video player (bottom left) showing a recording of the application with a different theme. Notice the rich windowing behavior and the translucency on the widgets and the widget library itself. I could not fit all features in a window, click these links for missing features: 3D navigation, XPS help files, Visual Brushes in taskbar, detailed 3d chart.
Smart Office goes far beyond basic application skinning (where you can select the theme/color for your app). Smart Office allows you to create custom styles to effectively visualize your data; end-users can create client-side presentation rules with out writing any code!
Rich windowing behavior; 100% customizable and adaptive.
Most of the screens use “smart, dynamic layouts”. The user determines the size and position of a screen, which of course the application automatically remembers; beyond positioning and sizing, the window dynamically scales its content to take advantage of the real-estate available and if you do not like it, no problem, you can manually control the scale of each screen.
Overall, the application is brilliant. Huge kudos to the teams involved in creating this amazing productivity suite. I am looking forward to other companies –including Microsoft- creating more applications like this one..
Geeking it out (with non visual details): The application is a great S+S showcase. The client is a Full-Trust Click Once deployment with autoupdate functionality. The back-end is a Java Service Layer talking to all kinds of code (.NET, Java, main-frame, you name it). The Office Add-ins are installed on demand or based on user preferences. The application is huge! Over 8000 screens and growing. This is of course meta-data driven but as you can tell from the stunning visuals they do a great job at leveraging styles, data and control templates, to create a great UX. Will try to get the technical folks next time they are in Redmond to share all the lower-level details!
More write-ups and showcases coming There are lots of other great WPF applications out there; but little time do record every one… I am hoping to showcase at least one video per month. If you want to be in the list for showcases drop me an email, we always love to see how others use the technology.
ttfn. Huge thanks to Matt for stopping by to record this and to Adam for doing the interview!.
Last week I recorded a PDC CountDown video about the Pre-conference seminars at PDC2008.
Going into the recording, I knew I would have to keep it very high level (since the format for the whole recording is < 10 mins and there are important PDC announcements first, so I was couting on 8 minutes, and it takes me 3 to introduce each speaker, these folks have too many accomplishments). Now that I have no time constraints, here is a detailed introduction to the PDC pre-cons. The top part of this writing is a bit of high-level reasons why you should attend the pre-cons? Underneath that section, you can find the "inside the pre-con" planning, goals, processes, and raw session details as I see them (not as the polished abstracts describe).
<marketing> What is a pre-con? An all day training the day before PDC.
What should you expect if you attend a pre-con?
More info? Official abstracts and bios for the speakers at the PDC pre-con website.
</marketing>
<themeat> Now the inside story to the Pre-cons..
The goals: There were three key tenets at the core of pre-con planning:
Selecting the sessions I started with ~50 topics. I was all over the map, XNA, HPC, WPF, WF, Sharepoint, Live, F#, LINQ, etc. Then I mapped them to PDC tracks (sorry, afaik I can't share these but I can say there were four), for pre-con we added an extra track called "industry/fun" track; which aimed at doing some thing different from what you expect at PDC. We quickly narrowed it from 50 to 25 by applying a rigorous quality criteria:
When I was down to 20-25 sessions, we shared them with other teams at Microsoft and with an "Advisory board"; the advisory board is mostly Regional Directors; all of them trainers/influentials and community leaders. It was great to hear what they thought the community needed; it was quite different from what I was expecting, but they provided great info to back up their arguments, so we listened!
After the prioritization, we went after great presenters for each topic. If we did not find an absolute rock-star (inside or outside Microsoft ) we cut the topic.
In the end, there were still a few painful cuts. ASPX/MVC stuff, XNA, new MFC/Win32 stuff, and a few others. There was a an important goal to achieve: variety! . Some people inside Microsoft didn't get that it did not matter not to have the 10 most popular, but we needed to have some thing for everyone! I could agree that ASPX would have had more attendees than say "advanced debugging", but I needed to offer some thing to the C++ developers, and the web developers already had Silverlight, you get the drill. "Something for everyone" was maybe a secondary tenet.
The sessions themselves and the dynamics: Why the session, why the speaker(s)? I aimed to add context to each session, without repeating the abstracts and speaker bios. Please do read those before reading the ramblings below; my thoughts below are meant to add context on why the speaker or topic, but I did not repeat the outline or objectives for each session.
</themeat> Registering for pre-cons. Now that you have seen the line-up; I hope you do consider joining. It is going to be fun and enlightening. I promise! I have to close with an FAQ that has come up several times already: if you already registered for the conference and need to change your registration so you can attend the pre-con, it is possible to do it, just email PDC2008@ustechs.com and they will help you.
Feedback/Suggestions: If you have a suggestion for improving the pre-cons feel free to email me via the blog. We welcome all suggestions!
That is it (for now)! C U at PDC Pre-cons!
As I mentioned on the SP1 cheat sheet, client profile is an exciting new deployment feature in SP1.. Troy Martez has an intro paper and a deployment guide for it. Those docs and his coming blog are the ultimate reference on client profile, but I wanted to share the whole context and my 2c on the subject [because I have seen a lot of questions on the feature]. [For most of you readers half of this is old news, feel free to skip to the highlighted sentences].
The motivation for client profile:
The .NET run-time has gotten big over time. The growth was positive- we got WPF, WCF, Card Space, LINQ, etc- but the trade-off was increased download size and increased install time . The bad rep on download size is compounded by how we package the off-line installers. For example, the off-line 3.5 SP1 installer, is ~230 MB. The reason for it is because we ship x86, x64, and ia64 bundled together. The reality is that if you used the 3.5 SP1 boot-strapper and installed online, you would get 1/3 of that size, with a ~3 MB bootstrapper (instead of the whoopy 200mb).
Introducing client profile
The idea is simple: 1) package the subset of the framework that is most commonly used by client apps. 2) install that subset. 3) later (on the background preferably), upgrade the subset installed to a full .NET 3.5 SP1. This package in #1 above is the client profile SKU. Client profile includes WPF, Windows Forms, WCF, the BCL, data access, etc. (for a full manifest, check Justin's post). The net result is that client profile gives you a .NET run-time with an initial download of ~28 MB and install time much shorter than full .NET framework.
The details on the implementation If you read #1 above, client profile is a subset of the framework. The more accurate explanation is that it is a subset of .NET 1.1x + subset of 2.0 + subset of 3.x so unfortunately we can not install client profile on a machine that already has a .NET framework installed.
If you see above again, step #3 is upgrading the framework from client profile to full .NET 3.5 SP1.. how does it happen?
The temporary gotcha .NET 3.5 SP1 shipped last week, and client profile is available for download but Windows Update does not start updating machines to .NET 3.5 SP1 until they complete their testing - 4 to 6 weeks from now, we hope-. So if you installed client profile today you would not be upgraded automatically like we planned. Because of this reason, we are labeling the Client Profile release as "Preview" until Windows update begins upgrading systems to 3.5 SP1.
This does not mean the run-time with change; the run-time is frozen. We have the preview out for people to start testing their app against client profile and planning the deployment; we just recommend that you wait and release a client profile app only after Windows Update begins upgrading systems to 3.5 SP1.
Other FAQs: A point of confusion is the packaging. Client profile is ~28 MB, but if you go to download it, you will see two options available:
I am sure there are a lot more questions you will have (like how do I create an app that targets client profile). I will come back to client profile later, or at least point you to Troy's blog since he is working on explaining all of these. For today, I just wanted to explain the platforms and scenarios that client profile aims to address, its relationship to Windows Update, and the explanation on the off-line installer's size (this was causing confusion, at least for me). For the record a benefit I did not tout today is that client profile lets you customize the UI for the install experience; I will have to come back to that one since it is neat-o.
Tim is OOF and his automated response forwards WPF requests to me.. want to know the most FAQ was last week ? Where is the hands-on-lab for building the Outlook UI using WPF?..
Answer: It is here. Give me a few days to look through it and ask Ronnie –the author – if we should post it on windowsclient.net too..
There was 6 requests for it last week, which is great because it confirms some thing we are thinking today: we need more WPF reference applications.
We do have Family.Show, but are thinking of a new one. Should we??
If so, what is the scenario? should it be a LOB or a consumer scenario? high-end graphics? Do you really need step-by-step HOL?? Or would a slightly higher level write-up explaining all the trade-offs and best practices do?? [we are leaning for the latter]..
Let me know via comments or email…
Thanks!!
PS – if we move it or add it to windowsclient.net I will put it in the comments for this post to avoid an extra post… I tried to do that on Tim’s original post but new comments were disabled..
.NET 3.5 SP1 buzz peaked very early at the beta. At the time I was immersed in Silverlight, so I am now having to catch up; which is a bit of work since the release is packed with lots of new features. Below is my cheat sheet to date; I tried to group them on what I saw were the "core" investments.
The official release notes on 3.5 SP1 is at windowsclient.net/wpf. Tim Sneath has a great post that puts the enhancements into context both on size ( # of changes) and on impact ( based on customer feedback ) .
Deployment
Performance:
Graphics:
AppModel:
Tools/Other:
Data:
The list above is not all comprehensive, but it can help you catch up. Please let me know what I missed [I am sure there is lots of that]...
In this third and final part of the datagrid series ( part1, part 2) we get into styling the datagrid a little bit. This part is not an all comprehensive tutorial on styling datagrids, I will just touch on what we did for my sample and share a few tips & tricks.
Here is the final look.
Here is the declaration for the whole data grid.
<dg:DataGrid ItemsSource="{Binding Data}" Margin="20" AlternationCount="2" RowStyle="{StaticResource RowStyle}" AutoGenerateColumns="true" Grid.RowSpan="1" x:Name="BigKahuna">
If you see above, I templated the Header. All I wanted was a blue background with white foreground.
<Style x:Key="ColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}" BasedOn="{StaticResource {x:Type dg:DataGridColumnHeader}}"> <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Header}" /> <Setter Property="Foreground" Value="White" /> <Setter Property="HorizontalContentAlignment" Value="Center" /></Style>
<Style x:Key="RowStyle" TargetType="dg:DataGridRow" > <Style.Triggers> <Trigger Property="AlternationIndex" Value="1" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt1}" /> </Trigger> <Trigger Property="AlternationIndex" Value="0" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt0}" /> </Trigger> </Style.Triggers> </Style>
One customization that I did not do in the demo, but is probably common is tweaking the selection. By default DataGrid does a blue highlight, imagine you want to change that color to a green; you would just need to override the Template for DataCell.
<Style x:Key="CellStyle" TargetType="{x:Type dg:DataGridCell}"> <Style.Triggers> <Trigger Property="IsSelected" Value="True"> <Setter Property="Background" Value="#FF3FC642" /> </Trigger> </Style.Triggers> </Style>
The rest is tips & tricks for designers.
If you look in the obvious place ( Edit Template) Blend does not have a lot of design-time support for all the pieces in the datagrid because the parts to customize are not quite parts of the ControlTemplate, but for the most part a few of these are DataTemplates, so you can workaround by creating any ContentControl, and editing the ContentTemplate there. To edit the parts of the DataGrid's controlTemplate, you can actually drop and individual parts (like DataGridHeader) of the template in a regular window and edit their style there.
The only 'tricky' one you might run into is DataGridHeaderBorder (because it does not expose a template). My advise there is to go ahead and select Edit Style -> Create Empty. Treat it as you would a border. Window4.xaml in the sample code has a small example of editing the pieces. [it is not complete by any means. It is also not pretty].
That is it in terms of getting my little portfolio data styled and it closes the DataGrid series. I hope it is useful to some one reading it. It was fun playing with the DataGrid. You don't realize how big this thing is until you play with it and see all the small, yet meaningful options it has.
For feedback, best place is the codeplex discussion . This build is a CTP, but it is quite functional and near completion. The control will ship out of band so don't wait too long; it will be shipping soon.
In part 1, I walked through some of the features in datagrid. The source for the series is here.
In this part, we will create a UI that looks like this:
I will mostly highlight the interesting parts [instead of going on a step by step]. The source is available and it would be repetitive if I go step-by-step. One thing to note is that (approximately) 90% of the work to customize it is in XAML. Some of it I did in code just to illustrate a point. DataGridTextColumn bindings were the simplest ones. You can assign a DataFieldBinding -- which is an actual Binding to the data you want. I liked the approach of passing the full binding [instead of a property name] because it allowed me to pass parameters like StringFormat (see below) and converters and other similar binding features. Nice!
<dg:DataGridTextColumnDataFieldBinding="{BindingDescription}"Header="Description"/> <dg:DataGridTextColumnDataFieldBinding="{BindingQuantity}"Header="Quantity" /> <dg:DataGridTextColumnDataFieldBinding="{BindingQuote,StringFormat={}{0:C}}"Header="Quote" />
<dg:DataGridHyperlinkColumn DataFieldBinding="{Binding SymbolUri}" ContentBinding="{Binding Symbol}" Header="Symbol" SortMemberPath="Symbol"/>
If you run the app, you can also see the "edit' behavior for DataGridHyperlinkColumn. You can edit the Uri, but not change the actual text (ContentBinding). You can manipulate it programmatically, but not from the editing experience.
Today's change column, I implemented as a DataGridTemplateColumn.
<dg:DataGridTemplateColumn CellTemplate="{StaticResource DailyPerformance}" Header="Today's Change" SortMemberPath="DailyDifference" />
A DatagridTemplateColumn is one where I can apply a CellTemplate so that it generates the UI. I first chose it for this column because I wanted to implement the behavior of highlighting gains ( >0 ) with Green and losses ( <0) as Red using a DataTemplate.Trigger, but that did not work so I ended up using a Converter. Hind-sight this is likely a better solution [more performant] any way.
<DataTemplate x:Key="DailyPerformance"> <TextBlock Text="{Binding DailyDifference, StringFormat={}{0:C}}" Foreground="{Binding '', Converter={StaticResource StockToBrushConverter}, ConverterParameter=IsDailyPositive }"> </TextBlock> </DataTemplate>
Notice that I was still able to use a SortMemberPath on the DataGridTemplateColumn. This is really nice because regardless of what my UI looks like I can still sort the data.Total Gain column uses the same technique than Today's change. Rating column is a little gaudy on purpose.
<dg:DataGridTemplateColumn CellTemplateSelector="{StaticResource StarsTemplateSelector}" Header="Rating" SortMemberPath="Rating"/>
Here I used a TemplateSelector just for illustration purposes. The selector is trivial. All it does is look for a template in a resource dictionary for the datagrid.
public class StarsTemplateSelector : DataTemplateSelector { public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container) { StockXAction sac = item as StockXAction; FrameworkElement fe = container as FrameworkElement; if (sac != null && fe != null ) { string s = sac.Stars.ToString() + "StarsTemplate"; DataTemplate ret = fe.FindResource(s) as DataTemplate; return ret; } return base.SelectTemplate(item, container); } }
From the XAML, you can also notice the SortMemberPath again. The UI now has Star ratings on it, yet I can still sort and did not have to write any code !!
Separator Columns are empty 'dummy' columns I added just make empty space to separate the colums from autogenerated ones. See Autogenerated Columns below for why. Autogenerated columns I wanted to leave AutoGenerateColumns="true" so you could see how the 'raw' data turns into the view. It is also nice because you get to see some of the Column types I did not use for example the ComboBoxColum -- you can see it on the Autogenerated rating column. It is an enum, and it turns into a ComboBoxColumn.
Default data for new rows If you scroll to the bottom and a new row [functionality that comes out of box]. You will see this:
The NaN is a problem. What happens is here is it is trying to calculate Gain, but data has not been initialized.
The workaround is to handle the DataGrid's InitializeNewItem. This will be called as a new record is initalized.
this.BigKahuna.InitializingNewItem += new InitializingNewItemEventHandler(BigKahuna_InitializingNewItem);
void BigKahuna_InitializingNewItem(objectsender,
InitializingNewItemEventArgs e) { //cast e.NewItem to our type StockXAction sa = e.NewItem as StockXAction; if (sa != null) { //initialize sa.Symbol = "New data"; sa.Quantity = 0; sa.Quote = 0; sa.PurchasePrice = 0.0001; } }
Copying data on DataGridTemplateColumns
Another issue you would notice is that if you do a Copy (Ctrl-C) or right click into Context menu which I added, the TemplatedColumns are not copied by default. What I needed to handle in order for copying to work is to pass a binding to ClipboardContentBinding. So we can tweak the template we had earlier and I will be tricky and pass the enumerator (Stars).
<dg:DataGridTemplateColumn CellTemplateSelector="{StaticResource StarsTemplateSelector}" Header="Rating" SortMemberPath="Rating" ClipboardContentBinding="{Binding Stars}" />
Now when I copy paste, I do get the value generated from ToString() on the enumerator.
One more thing to mention around Copying is that Data*Column has a CopyingCellClipboardContent event. This is good for overriding the value if I did not have a binding; what I noticed on this build is that if there is no binding set on ClipboardContentBinding, the event is not firing. This will be fixed by RTM, interim just pass any binding (like {Binding}) and when the event fires you can override the value that will be cut & pasted from code.
OK, that covers most of the functionality. In part 3 we can take care of the styling.
On Monday, the WPF team released the CTP of their new datagrid control. You can download it from here. [Note that it requires .NET 3.5 SP1, released monday too]
I have been playing with it so I created this 3 part series.
The source for this sample is here.
Getting Started was trivial.
<Window x:Class="WpfApplication1.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:dg="http://schemas.microsoft.com/wpf/2008/toolkit" Title="Window1" Height="300" Width="300" >
I had some 'dummy' data simulating financial transactions so I took advantage of AutoGenerateColumns feature in the grid to get a quick 'fix':
The results were quite rewarding for a line of code. I could Reorder the columns, Resize the Columns, Sort by Column, Add New Rows , Edit the data, Select Rows , and Copy to Clipboard. I then moved on quickly to styling it a little bit.. Like every thing else WPF, the datagrid is incredibly flexible on customization by using styles and templates. In a few minutes, I hand-wrote this
<Window.Resources> <SolidColorBrush x:Key="DataGrid_Style0_Header" Color="#FF4F81BD"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt0" Color="#FFD0D8E8"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt1" Color="#FFE9EDF4"/> <Style x:Key="ColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}"> <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Header}" /> <Setter Property="Foreground" Value="White" /> </Style> <Style x:Key="RowStyle" TargetType="dg:DataGridRow" > <Style.Triggers> <Trigger Property="AlternationIndex" Value="1" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt1}" /> </Trigger> <Trigger Property="AlternationIndex" Value="0" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt0}" /> </Trigger> </Style.Triggers> </Style> </Window.Resources> and got this:
<Window.Resources> <SolidColorBrush x:Key="DataGrid_Style0_Header" Color="#FF4F81BD"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt0" Color="#FFD0D8E8"/> <SolidColorBrush x:Key="DataGrid_Style0_Alt1" Color="#FFE9EDF4"/> <Style x:Key="ColumnHeaderStyle" TargetType="{x:Type dg:DataGridColumnHeader}"> <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Header}" /> <Setter Property="Foreground" Value="White" /> </Style> <Style x:Key="RowStyle" TargetType="dg:DataGridRow" > <Style.Triggers> <Trigger Property="AlternationIndex" Value="1" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt1}" /> </Trigger> <Trigger Property="AlternationIndex" Value="0" > <Setter Property="Background" Value="{StaticResource DataGrid_Style0_Alt0}" /> </Trigger> </Style.Triggers> </Style> </Window.Resources>
and got this:
[We will cover styling in part3, let's first walk through all features]:
Selection Unit: Under the hood with the grid, you are really selecting cells, but the grid has a couple nice modes to make it easier for developer to use the concept of selected rows:
o SelectedCells has all the selected cells, including all the cells in a selected row when a row is selected through the RowHeader
o SelectedItems has the rows which are selected through the RowHeader, or is empty if only cells are selected (i.e., highlighting all the cells in a row will not add that row to SelectedItems)
Selection Mode: In Single mode, I can choose a single unit (see above for unit) and in Extended Mode I can select multiple units. The usual keyboard navigation short-cuts apply (Shift takes you to the end of current row, Ctrl preserves previous selection, etc.)
GridLines
Using GridLinesVisibility property, I can choose which gridlines are visible: All, Horizontal, Vertical, and None. I can also choose the color for the horizontal and vertical gridlines.
Headers (Row & Columns)
By tweaking the HeaderVisibility property, I can choose which headers are visible: All, Column, Row, None. The headers for each column can be customized/styled using a HeaderTemplate.
Column operations
Autogeneration of columns works quite well. The default mappings are:
*the ComboBoxColumn is created only if the field is writeable ( which happens when the property is not read only). For other types (e.g. DateTime or objects) the DataGridTextColumn will be the default with a ToString() on the object. You can customize AutoGeneration of columns by handling the AutoGeneratingColumn event. You will see this in part2.
ReadOnly columns is missing from the CTP, but that is not a huge problem, you can easily accomplish ReadOnly behavior by using DataGridTemplateColumns and replacing the templates with read-only controls ( like TextBlocks).
Column Resizing and Reordering is implemented out of the box and is toggled on/off via the CanUserReorderColumns and CanUserResizeColumns respectively. If you want to control reorder per column, there is a CanUserReorder property on the column itself.
Frozen columns. A frozen column is one that does not scroll out of view when the user scrolls in the horizontal direction. When a column is frozen every column displayed to its left are also frozen. Frozen columns is supported out of the box. You can control it by setting the IsFrozen property on a column.
You can see frozen columns in the demo I created by right clicking and showing the ContextMenu.
Row operations
Adding new rows is supported. You can enable it via CanUserAddRows. Deleting rows is supported too, controlled via CanUserDeleteRows.
For alternating rows, the datagrid has an AlternationCount property for controlling AlternateRows. The way it works is you set AlternationCount to the total number of styles/colors to be used. Usually this is two colors, truly alternating, but it could be more colors if needed [and you like to get funky] Once AlternationCount has been set, on your RowStyle you can create a trigger that checks AlternationIndex (which should be 0 to AlternationCount-1) and set the style there.
Editing Cells
Before getting into editing, I have to comment on entering Edit Mode. The Datagrid requires you to have focus in the cell in order to get into edit mode. To get focus, you can click on a cell, or tab into it. Once you have focus, the most common gestures to get into edit mode are supported:
For programmatically manipulating the cell with focus or during edit mode, the datagrid has a property of CurrentCell and each DataGridCell instance has an IsEditing property
You can customize the Editing experience for any column by providing a CellEditingTemplate. [If you used one of the stock columns listed above, those automatically provide a template]. Editing a cell has three commands that are fired as you get in and out of edit mode:
Other features Copy to Clipboard is implemented. Ctrl-C works fine. The data format appears to be tab delimited. Which is nice as it works seamlessly with excel. Keyboard Navigation [by using arrows and tab] works out of the box.
Some missing features already announced: The big one is RowDetails. I hear it is already in later builds, so the expectation is that it will be in by RTM. ReadOnly columns, and support for hidden columns. [Though I am thinking for those there is workarounds today]. Bugs along the way and known issues.
The only one I ran into is that DataTemplate.Triggers is not working on this build. I hear it will be working on later builds. Show me the code (or demo). Playing with all the features above is easy. The sample app I Created has a little bit of UI data bound to the datagrid that lets you manipulate the grid to see most of the features above.
In Part2, we start using these features to build a more 'insightful' view of the data.
In the Deep Zoom run-time, you can load two types of compositions:
Collections have a lot more flexibility of course, but I also caution of two tiny concerns:
This post is about working with Collections, so let's assume I decided the two issues above are not in play (that is the case for most people). To find out what we can do with a MultiScaleSubImage, all we need is to look at the class definition:
A few of the less obvious options for dealing with collections are:
Tags If you have used Deep Zoom Composer you might have noticed that composer has a property called "Tag" for each image. As of beta2 tag is not exposed via the MultiScaleSubImage. So, how can you get to this Tag?
The tags are stored in the metadata.xml file generated by Deep Zoom Composer. You can easily get to this file using WebClient or HttpWebRequest networking APIs in SL2. It is a trivial two step process:
void wc_DownloadStringCompleted(objectsender, DownloadStringCompletedEventArgs e) { if(e.Cancelled == false&& e.Error == null) { strings = e.Result; XDocument doc = XDocument.Parse(s); var images = froma indoc.Element("Metadata").Descendants("Image") selecta; foreach ( XElement image inimages ) { CollectionImage ci = newCollectionImage { Height = (double) image.Element("Height"), Width = (double) image.Element("Width"), //here we read the ZOrder from metadata.xml and subtract one ZOrder = ((int) image.Element("ZOrder")) -1 , Tag = (string) image.Element("Tag"), Location = newPoint{ X = (double)image.Element("x"), Y = (double) image.Element("y")} } ; //here we map from the SubImages collection based on the ZOrder we read ci.Image = msi.SubImages[ci.ZOrder]; _images.Add ( ci ) ; } items.ItemsSource = _images; } } If you look at the code, I created a CollectionImage which aggregates the stuff from metadata.xml and the corresponding MultiScaleSubImage. This means I could now filter, or do any thing since the data is merged. Kirupa has an example of using tags to filter (so I will stop here on that topic and move to Viewports).
void wc_DownloadStringCompleted(objectsender, DownloadStringCompletedEventArgs e) { if(e.Cancelled == false&& e.Error == null) { strings = e.Result; XDocument doc = XDocument.Parse(s); var images = froma indoc.Element("Metadata").Descendants("Image") selecta; foreach ( XElement image inimages ) { CollectionImage ci = newCollectionImage { Height = (double) image.Element("Height"), Width = (double) image.Element("Width"),
//here we read the ZOrder from metadata.xml and subtract one ZOrder = ((int) image.Element("ZOrder")) -1 , Tag = (string) image.Element("Tag"), Location = newPoint{ X = (double)image.Element("x"), Y = (double) image.Element("y")} } ;
//here we map from the SubImages collection based on the ZOrder we read ci.Image = msi.SubImages[ci.ZOrder]; _images.Add ( ci ) ; } items.ItemsSource = _images; } }
If you look at the code, I created a CollectionImage which aggregates the stuff from metadata.xml and the corresponding MultiScaleSubImage. This means I could now filter, or do any thing since the data is merged. Kirupa has an example of using tags to filter (so I will stop here on that topic and move to Viewports).
ViewportOrigin:
ViewportOrigin represents the left(x), top(y) corner of your SubImage relative to the MultiScaleImage control. The surprise (for me at least) is that:
Got it?? OK! you are done. If you are like me you might want to see a sample. Here are some below:
Again, you can play with the ugly but hopefully useful sample here.. Just change the ViewportOrigin, or any other property and see what happens. You can use the same sample to play with Opacity, ZIndex and ViewportWidth.. this will show you the flexibility in collections. Don't get tricky with the values as there is no validation.
Mapping SubImages to Element Coordinates Now that we understand ViewportWidth and ViewportOrigin, we can map from logical coordinates to element coordinates so we can put overlays on our MultiScaleImage. Or do hit testing or any thing similar.
What I did is put a small pink rectangle in the page and I am going to listen to MouseMove on the MultiScaleImage and then do kind of a "hit testing" to see which Image I am over. I used ZIndex to select only the single image on the front. If you did not use ZIndex you can select multiple. So, what does the map look like?? The whole code is below commented in detail.. I hope that makes it easiest to explain -instead of my rambles-.
/// <summary> /// Gets a rectangle representing the top-most image that the mouse is over /// </summary> /// <param name="elementPoint">Mouse Position, in Element Coordinates</param> /// <returns> Rectangle reprsenting Element Coordinates for the image or 0,0,0,0 if not over an image</returns> Rect SubImageHitTestUsingElement(Point elementPoint) { Rect resultRect = new Rect(0, 0, 0, 0); int zIndex = 0; // We loop through all our images. for (int i = 0; i < _images.Count; i++) { try { // Selct our MSSI. MultiScaleSubImage subImage = _images[i].Image; // NOTICE the scale is a mutliplication of the size of our image (1 / subImage.ViewportWidth) // and the current Zoom level ( 1 / msi.ViewportWidth) double scaleBy = 1 / subImage.ViewportWidth * 1 / msi.ViewportWidth; // The two lines below convert our image size us from Logical to Element Coords // Notice that for Height, we must take into account Aspect ratio. double width = scaleBy * this.msi.ActualWidth; double height = (scaleBy * this.msi.ActualWidth * (1 / subImage.AspectRatio)); // Now we convert our viewportorigin (logical coords) to Element Coords // Reminder, this is top-left .. Notice that we multiply by -1 since // we saw the negative math for Viewport Origin. Point p = msi.LogicalToElementPoint( new Point( -subImage.ViewportOrigin.X * 1 / subImage.ViewportWidth, - subImage.ViewportOrigin.Y * 1 / subImage.ViewportWidth)); // Now we create a Rectangle in Element Coords. Rect subImageRect = new Rect(p.X, p.Y, width, height); // here we hitTest, using Contains.. // and we keep track of the front-most element only.. if (subImageRect.Contains(elementPoint)) { if (subImage.ZIndex >= zIndex) { zIndex = subImage.ZIndex; resultRect = subImageRect; } } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine(ex.Message); } } System.Diagnostics.Debug.WriteLine("Done"); return resultRect; }
I used Element Coords because that is what I was after. If you want logical coords, it should be easy from code above.. Just convert the point to Logical, do the scaling for zoom and hittest against a logical rect... Fair enough??? The source is [you guessed it] at Skydrive.
You can see a few tiny issues I did not care much for: 1) My math is rounded so some times you see the 'Rectangle' I created be slightly off (adding some extra pixels should do fine) ... 2) I did the recalculation for rectangle only on mouse move.. and I did not do it on Pan... so if you Zoom using Wheel or you pan, it will take for you to move the mouse one more time in order for Rectangle overlay to update.
That is my part!! Now it is up to you to build some thing really cool using real images and hittesting..
A few weeks ago I noticed changes in browser's behaviors…
1) My IE 7.0.6000 no longer prompts for Click To Activate :)
2) My firefox 2.0.0.13 no longer required a reboot after installing Silverlight … If you had not noticed, using silverlight.js on IE allowed you to instantiate silverlight plugin right after installing , but on other browsers it required a re-start…
I pinged Piotr, our deployment PM, asking if he was seeing the same and he upped it sharing that he had a way to instantiate silverlight on ANY PLATFORM and ANY browser WITH OUT requiring A RE-START… [call me a geek, but I was happy].. Today, he posted the blog with the magic call: navigator.plugins.refresh () … Check it out yourself… I hear the next version of Silverlight.js (when we next release an SDK) will include these changes; in the mean time you can take his advise and use navigator.plugins.refresh () to get around the re-boot issue and instantiate SL right after installing…
[caveat: this does not work if silverlight is being updated, and the plugin was loaded, but that scenario is not common since silverlight auto-updates itself] …
Thanks Piotr, and welcome to blogging..
On May 17, Karl Shifflett and Josh Smith present their “WPF Multi-Tier Business Application Track at the Enterprise Developers Guild Code Camp”. The event is at the CPCC Central Campus located in Charlotte, NC.
If you are in the area or within driving/train distance I definitely recommend you try to attend… Josh and Karl are two of the most knowledgeable and passionate WPF experts out there.. I have had the pleasure of seeing Josh present at the 08 WPB bootcamp and he is very good – he has this funny/casual way of sharing a lot of deep experience.. I have not seen Karl speak yet, but given his Mole accomplishments there is no doubt this event is going to be very insightful… [btw, if MS has another WPF bootcamp in the future, we will have Karl :)]
I wish I could be there for this.. but I have a conflict ;( …. if you are in the area sign-up soon, attend and have lots of fun … [there will even be prizes]
Good luck Josh & Karl!!! Thanks for volunteering and sharing!
J
Viewbox is a pretty handy 'container' in WPF.. It is a decorator that scales its child content to the size available to the viewbox ( if child is smaller it scales it up >1, if child is bigger it scales it down such that it fits based on some stretch direction).
You can find source code for a sample viewbox here. If you want to see a ugly (yet still useful for those knowing viewbox) harness, there is a sample here.. Sorry for no docs; the control itself is straight forward. Please check the docs for WPF viewbox to see how to use it; the SL2 version above mimics WPF. Please do review the source if you are using it for a real project.
Cheers,
This one got stuck on my drafts on 4/10… sorry … (FYI, I am back from my trip; more on that soon ) …
- –- -—-— -- -
I am out all next 1.5 weeks talking to ISVs in EMEA about WPF and Silverlight… Should be so much fun that I won’t have time to blog :(.. in the mean time, here is an OPML of the Silverlight blogs I read…
If your blog is missing or you want to recommend some one else’s blog, please email me or leave comments…
Again, OPML is here.
Most people already know (from ScottGu’s blog post for example) that in Silverlight 2 you can override the ControlTemplate for a Control and ‘re-define’ the look of the control. However, I have received a few questions around the use of generic.xaml to accomplish this same task; I will try to share a few thoughts on this to tease you into digging deeper on your own. If you are short on time, skip to [FAQs on built-in styles below]
Some definitions on the recurrent “what is difference between style and template?”
Style is an object (in Markup or in Code) that sets properties of a control. All that a style can do is set the value of existing properties on the control. Imagine our control was a Car, a Style could say some thing like “wheelsize=17”, bodycolor=”cherry red”, “windowtreatment=”tinted”, etc..
A template actually defines the pieces or parts of the car. For example, a template for a cheap convertible might not have a roof at all :) or a template for a Car can decide if it is two doors or four doors, if it has four wheels or eight, etc. When I explain it I always tell people, the template defines the skeleton, the Style dresses the pirate ( I like the pirate analogy cause some of them have one eye, or one leg, or one arm, etc. making good use of Templates).
Where things get interesting is that a Style can set any property in the control, and Template is itself a property, so what you see the tools (like Blend) do most often is when you want to override the Template of a Control, they override the whole style and the Template property with it.
Stay with me… even if the above does not make sense, the rest of the article will help.
Where generic.xaml comes in is in the “magic” that defines the default look for a control. Let’s imagine we want to create a GelButton ..
public class GelButton : Button { }Simple enough, now we want to use it in our Page.xaml user control, we add the namespace and the control.
<UserControl x:Class="StylingSample.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300" xmlns:samples="clr-namespace:StylingSample"> <StackPanel Width="50" > <Button Content="Top" Height="50"/> <samples:GelButton Content="Cream" Height="50"></samples:GelButton> <Button Content="Bottom" Height="50"> </Button> </StackPanel> </UserControl>
Would you be surprised if the outcome looked like this?
I can’t tell you if you should be surprised or not (I am undecided myself), but I can tell you what happened! The control by default is lookless. You need to define the look for it. This is accomplished by assigning a valid ControlTemplate to the control [via the Template property in the Control class].
To assign the Template property, you could do some thing like:
public GelButton (){ this.Template = XamlReader.Load ("<ControlTemplate xmlns='http://schemas.microsoft.com/client/2007' ><Grid ..> </Grid></ControlTemplate>");
}
but a better way to do it is to store the control template in a generic.xaml Resource Dictionary and then magically the run-time, will pick it up from there. Your template would be associated to your control via the TargetType attribute when defining the resource. This template would now become what we call the “built-in style”.
Here are the details on creating a built-in style.
Generic.xaml is a ResourceDictionary –a property bag for resources – that you include in your assembly, at the root of the project. If you are a WPFer you might be thinking it should be in themes\generic.xaml, I hear that is where it might end up, but for now (Silverlight 2 beta1), it needs to be in the root of the project. The default (empty) resource generic.xaml could look like this:
<ResourceDictionary xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > </ResourceDictionary>
For defining the look & feel for our GelButton we need to start with some default template. Long term, this will be a right click inside Blend (like in WPF); temporarily since Blend does not yet support styling, I would recommend is using David Anson’s handy Stylebrowser application to copy the default Style for button and paste it into the resource dictionary.
[unfortunately the default button template is too verbose, so for practical purposes here I am going to use a much simpler template].
<ResourceDictionary xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:samples="clr-namespace:StylingSample;assembly=StylingSample" > <Style TargetType="samples:GelButton"> <Setter Property="Background" Value="Black" /> <Setter Property="Foreground" Value="White" /> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="samples:GelButton"> <Grid x:Name="RootElement" > <Ellipse Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" Fill="{TemplateBinding Background}" /> <ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Foreground="{TemplateBinding Foreground}" VerticalAlignment="Center" HorizontalAlignment="Center" /> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary>
Let’s dissect the work needed to create this template:
Now, I can run the same code, with changing nothing other than the Resource dictionary I added and I get:
Since I did create a Templatebinding for background/Foreground , I can even have some fun.. After all, I promised some “meat”.. Need food!! sorry about that it is 1:30 PM ..
<UserControl x:Class="StylingSample.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="400" Height="300" xmlns:samples="clr-namespace:StylingSample"> <StackPanel Width="50" Margin="0,20,0,0"> <samples:GelButton Content="" Height="20.4" RenderTransformOrigin="0.5,0.5" Width="48.8" Canvas.ZIndex="2"> <samples:GelButton.Background> <RadialGradientBrush> <GradientStop Color="#FFF5DEB3"/> <GradientStop Color="#FFE0B05C" Offset="0.826"/> </RadialGradientBrush> </samples:GelButton.Background> </samples:GelButton> <samples:GelButton Content="Ham" Height="16" Canvas.ZIndex="1"> <samples:GelButton.Background> <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0"> <GradientStop Color="#FFD64141"/> <GradientStop Color="#FFE23939" Offset="1"/> <GradientStop Color="#FEDAB6B6" Offset="0.43299999833106995"/> </LinearGradientBrush> </samples:GelButton.Background> </samples:GelButton> <samples:GelButton Content="" Height="16" > <samples:GelButton.Background> <RadialGradientBrush> <GradientStop Color="#FFF5DEB3"/> <GradientStop Color="#FFECC06E" Offset="0.991"/> </RadialGradientBrush> </samples:GelButton.Background> </samples:GelButton> </StackPanel> </UserControl>
So, I just wasted 10 mins of your time and 40 of mine introducing you to generic.xaml and built-in styles. I had promised to answer a few questions, here they are:
What are the benefits of built-in styles, why use generic.xaml instead of hardcoding the template?
It is nice to store all your templates in a resource dictionary that you can easily swap – as opposed to having to do it in code-. Imagine you needed to create three themes for your app, doing it with hardcoded templates would be hard. Also, If you put your Template in the ResourceDictionary the template can now reference other resources in the dictionary itself.
Why is it that all examples I have seen are not using built-in styles? we are always told to apply the style inline from App.xaml
Built-in styles are designed for control authors, when you write a control, you provide look & feel. If you look at example above, I had to inherited from Button class. Most samples are purely styling a button, so they take a different approach. In the financials demonstrator, you inherited from Button and did nothing other than provide the built-in style, is this a best practice?
I liked that approach (but I come from an enterprise background where we create bloated frameworks that often inherit just to create an abstraction in case some thing changes later) ; the one benefit you get is you can use your button any where with out having to explicitly refer to a style. The disadvantage of course is that inheriting takes a bit of extra performance and memory; but this is pretty negligible from what I have seen. Again, I don’t call it a best practice, more of a personal preference for me. If you look at financials demonstrator now, I ended up adding a property later UseReflection, so now the button does have a reason to be its own class.
Built-in styles sounds like I can change it all in one place? I don’t want to crowd my code with <Button Style=”{StaticResource GelButtonStyle}” >.
That is right, if you can afford inheriting and the classes are not sealed. That said, after building a few solutions I realized I had a false sense of centralization [yes I made up the term]. The argument is
1) With built-in styles, I can change the style in one place. It is the same using App.xaml you change the style itself in one place for all. What you are replicating a lot is the name of the style, but the style itself is in one place.
When can I not use built-in styles? if the class was sealed or they had protected the Template property then you would not be able to use this.
Can I just create a generic.xaml and override the System.Windows.Controls templates with out inheriting? Not that I know of. It does not sound like a good idea; I tried it just to see if it worked and it did not work for me.
Is applying a built-in style going to break or affect my state and parts? No. As long as your style uses the same names, the code will still pick all that as if it was an inline Style. We would not need built-in styles if you allowed TargetType every where, including on regular dictionaries, like WPF does. Fair point, these features are all being considered for later versions after 2.0 stay tuned, right now this way works, it is flexible and comprehensive.
Will this approach work with Blend? Will I still be able to style in Blend.
Yes! Blend works with this already; that is how it picks the look & feel for System.WIndows.Controls today.
Why do styles & Control templates always go together? Can I just do my Control template? My personal opinion is that if you need a template and not the style your template might be too stringent or too hard coded; it would be the equivalent of writing a ControlTemplate that does not have any TemplateBinding on it; don’t get me wrong, I am not saying this is wrong, I am just saying 99% of the time, this does not happen. With regards to simply providing the ControlTemplate in generic.xaml, I don’t believe that would work.
In the financials demonstrator, you named your class Button, for some thing that inherited from Sysetm.Windows.Controls.Button.. Is using the same name required?
Absolutely not. I chose the name because I was going to override all controls, but I ended up changing my mind and that made it more confusing. Sorry about that; the name does not matter (as long as it does not conflict); from experience calling it Button will confuse you, don’t do that.
If I use the built-in style, does that mean a ‘consumer’ of my button will not be able to style the button later?
No! A consumer will still be able to style the button later and as well as override your template.
--
OK, I need to go eat. This at least answers the questions I had; will try to come back to this at a different time.
On my Deep zoom Post, I recommended that for Deep Zoom applications you instantiate the control using Silverlight.js to avoid the Click To Activate. Some one picked on this and asked why the new default is using OBJECT. Here is what I know (and most of it comes from past emails with Piotr Puszkiewicz).
Using the OBJECT tag has a few advantages:
<object data="data:application/x-silverlight," …> <!—This is the isntall experience –-> <a href="http://go.microsoft.com/fwlink/?LinkID=108182" style="text-decoration: none;"> <img src=http://go.microsoft.com/fwlink/?LinkId=108181 alt="Get Microsoft Silverlight" style="border-style: none"/> </a><!—- end of install --> </object>
So why did we default to <OBJECT /> ?? Well, because Click To Activate is going away. IE team announced it and given they said it was in april, we are within 28 days of this happening.. [Should we start a pool?].
For those of us creating samples today, we obviously have a choice for a few more weeks; I will remain partial to silverlight.js; if you want to come, here is how I get my instantiation:
#silverlightControlHost { height: 100%; width: 100%; }
html, body { height: 100%; overflow: auto; } body { padding: 0; margin: 0; }
You can get my sample default.html and silverlight.js from here.
Disclaimer: this approach is good for developer samples, for a production app please follow the Silverlight Installation Experience Guide.
Visifire has a free, open-source library for 2D and “3D looking” charts built using Silverlight 2.. Very cool!!
For me, this is obviously handy when creating demos and showing Silverlight capabilities; for commercial use they also have a commercial license..
Emailing them to see if they would be interesting on working with us for their charts to be styled in Blend; in the mean time enjoy the charts and play with them..
I had to learn DeepZoom recently and along the way I put together some handy notes .. below are the notes organized in a near step-by-step explanation format. This is a very long post, but I hope it has useful insights for any one wanting to do deepzoom so I recommend you read it all. If you must skip, then the outline will help you. imo, part 3 and 5 are the good stuff.
Part 1 – The history and brief explanation on how DeepZoom works.
Part 2 – Constructing a DeepZoom image using Image Composer
Part 3 – Introduction to the DeepZoom object model – goes way beyond the docs I hope
Part 4 – Coding a deepZoom ‘host’ user control with Pan & Zoom
Part 4.1 – Adding a few extra features to our User Control
Part 5 – Lessons learned on the code, documenting the gotchas **must read even if you know Deep Zoom already
Part 6 – Give me the code, just a zip file w/ the goodies
Part 7 – Show me the outcome; what did we build?
A lot of people equate DeepZoom to SeaDragon – they assume SeaDragon was the code name and DeepZoom is the marketing name-. This assumption is not quite right (unless you equate your engine to your car model). Seadragon is an incubation project resulting from the acquisition of Seadragon Software; the Seadragon team is part of the Live organization and are working on several projects (like Photosynth). DeepZoom is an implementation that exposes some of the SeaDragon technology to Silverlight.
DeepZoom provides the ability to smoothly present and navigate large amounts of visual information (images) regardless of the size of the size of the data, and optimizing the bandwidth available to download it.
How does DeepZoom work? DeepZoom accomplishes its goal by partitioning an image (or a composition of images) into tiles. While tiling the image, the composer also creates a pyramid of lower resolution tiles for the original composition.
The image to the right shows you what a pyramid; the original image is lowest in the pyramid, notice how it is tiled into smaller images, also notice the pyramid creates lower resolution images (also tiled). A few of the docs I read said the tiles are 256x256, but from peeking through the files generated by the composer I am not convinced; I do know from reading through the internal stuff that there is some heavy math involved here, so I trust they tile for right size :).
All of this tiling is performed at design-time and gets accomplished using the DeepZoom composer.
At run-time a MultiScaleImage downloads a lower resolution tile of the image first and downloads the other images on demand (as you pan or zoom); DeepZoom make sure the transitions from lower to higher res images are smooth and seamless.
Given all this, how is Deepzoom different than say a ScaleTransform (for zoom) on a high resolution image?
With a ScaleTransform, usually you would download the whole high res image at once; this delays how quickly the end user gets to see the image when the page or application loads. Some times people apply a trick where you use different resolutions images, since you are not tiled you will likely end up downloading several big images (consuming more network bandwidth) or the download time will continue to be high if the initial downloaded image is not small enough, also the transitions from low to higher res are going to be more noticeable unless your write the transitions yourself.
DeepZoom and its tiling make it possible to see bits quicker and can optimize for bandwidth. In the worst case scenario where some one looked at every single one of the tiles at the highest resolution, DeepZoom would have an extra overhead of 33% compared to downloading the single highest resolution image at once, but this ‘worst case’ scenario is almost never hit, most of the time DeepZoom can save you from downloading too much.
Another feature in DeepZoom is its ability to create ‘collections’ from the composite image. This provides you the ability to compose a scene ( group of images ), optimize them for speed & download, but still maintain the ‘autonomy’ and identity of the image, you can programmatically manipulate (or position) these images from within the DeepZoom collection (more on collections in part 4).
Once you have a DeepZoom image, you will need an instance of the MultiScaleImage class in your silverlight application to load that image. Instantiating the MultiScaleImage class can be done from XAML
<MultiScaleImage x:Name="DeepZoom" Source="easter/info.bin" />
or from code:
MultiScaleImage DeepZoom = new MultiScaleImage () ;
DeepZoom.Source = new Uri ( “easter/info.bin”) ;
Before going through the DeepZoom API it makes sense to understand the terminology used:
Now, we navigate through the interesting properties and methods in MultiScaleImage
The interesting methods are:
In my opinion, surprisingly missing from the API were:
The goal here is to code a sample reusable control just to illustrate the points; along the way we will of course implement enough features for our Easter Egg Hunt. [Update: Sorry about belatedness, I started this on 3/22 but had a trip that prevented me from playing around, so I am late from easter]
<UserControl x:Class="DeepZoomSample.Page" xmlns="http://schemas.microsoft.com/client/2007" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" > <Grid x:Name="LayoutRoot" Background="White"> <MultiScaleImage x:Name="DeepZoom" Source="easter/info.bin" /> </Grid> </UserControl>
using System; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Ink; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Shapes; using System.Windows.Browser;
namespace DeepZoomSample { // this code came from Peter Blois, http://www.blois.us/blog // Code ported by Pete blois from Javascript version at http://adomas.org/javascript-mouse-wheel/ public class MouseWheelEventArgs : EventArgs { private double delta; private bool handled = false;
public MouseWheelEventArgs(double delta) { this.delta = delta; }
public double Delta { get { return this.delta; } }
// Use handled to prevent the default browser behavior! public bool Handled { get { return this.handled; } set { this.handled = value; } } }
public class MouseWheelHelper {
public event EventHandler<MouseWheelEventArgs> Moved; private static Worker worker; private bool isMouseOver = false;
public MouseWheelHelper(FrameworkElement element) {
if (MouseWheelHelper.worker == null) MouseWheelHelper.worker = new Worker();
MouseWheelHelper.worker.Moved += this.HandleMouseWheel;
element.MouseEnter += this.HandleMouseEnter; element.MouseLeave += this.HandleMouseLeave; element.MouseMove += this.HandleMouseMove; }
private void HandleMouseWheel(object sender, MouseWheelEventArgs args) { if (this.isMouseOver) this.Moved(this, args); }
private void HandleMouseEnter(object sender, EventArgs e) { this.isMouseOver = true; }
private void HandleMouseLeave(object sender, EventArgs e) { this.isMouseOver = false; }
private void HandleMouseMove(object sender, EventArgs e) { this.isMouseOver = true; }
private class Worker {
public event EventHandler<MouseWheelEventArgs> Moved;
public Worker() {
if (HtmlPage.IsEnabled) { HtmlPage.Window.AttachEvent("DOMMouseScroll", this.HandleMouseWheel); HtmlPage.Window.AttachEvent("onmousewheel", this.HandleMouseWheel); HtmlPage.Document.AttachEvent("onmousewheel", this.HandleMouseWheel); }
private void HandleMouseWheel(object sender, HtmlEventArgs args) { double delta = 0;
ScriptObject eventObj = args.EventObject;
if (eventObj.GetProperty("wheelDelta") != null) { delta = ((double)eventObj.GetProperty("wheelDelta")) / 120;
if (HtmlPage.Window.GetProperty("opera") != null) delta = -delta; } else if (eventObj.GetProperty("detail") != null) { delta = -((double)eventObj.GetProperty("detail")) / 3;
if (HtmlPage.BrowserInformation.UserAgent.IndexOf("Macintosh") != -1) delta = delta * 3; }
if (delta != 0 && this.Moved != null) { MouseWheelEventArgs wheelArgs = new MouseWheelEventArgs(delta); this.Moved(this, wheelArgs);
if (wheelArgs.Handled) args.PreventDefault(); } } } } }
protected double _defaultZoom = 1.3; public double DefaultZoomFactor { get { return _defaultZoom; } set { _defaultZoom = value; } }
private double _currentTotalZoom = 1.0;
public double CurrentTotalZoom { get { return _currentTotalZoom; } set { _currentTotalZoom = value; } }
private double _maxZoomIn = 5000; protected double MaxZoomIn { get { return _maxZoomIn; } set { _maxZoomIn = value; } } private double _maxZoomOut = 0.001;
protected double MaxZoomOut { get { return _maxZoomOut; } set { _maxZoomOut = value; } }
/// <summary> /// Performs a Zoom operation relative to where Image is at. /// Example, call DoZoom twice with a Zoom of 1.25 will lead to an image that is zoomed at /// 1.25 after first time and ( 1.25 * 1.25 for second time, which is a 1.56 /// </summary> /// <param name="relativeZoom"> new zoom level; this is a RELATIVE value not absolute.</param> /// <param name="elementPoint"></param> void DoZoom(double relativeZoom , Point elementPoint) { if ( _currentTotalZoom * relativeZoom < MaxZoomOut || _currentTotalZoom * relativeZoom > MaxZoomIn) return; Point p = DeepZoom.ElementToLogicalPoint(elementPoint); DeepZoom.ZoomAboutLogicalPoint(relativeZoom, p.X, p.Y); this.Zoom = relativeZoom; _currentTotalZoom *= relativeZoom; }
void OnMouseWheelMoved(object sender, MouseWheelEventArgs e) { // e.Delta > 0 == wheel moved away, zoom in if (e.Delta > 0) { DoZoom( DefaultZoomFactor, _lastMousePosition); } else { // Zoom out DoZoom( 1/ DefaultZoomFactor, _lastMousePosition); } }
protected bool _isDragging = false; protected Point _lastDragViewportOrigin; void DeepZoom_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { this._lastDragViewportOrigin = DeepZoom.ViewportOrigin; this._lastMousePosition = e.GetPosition(DeepZoom); this._isDragging = true; }
void DeepZoom_MouseMove(object sender, MouseEventArgs e) { if (_isDragging) { Point newViewport = _lastDragViewportOrigin; Point currentMousePosition = e.GetPosition(DeepZoom); newViewport.X += (_lastMousePosition.X - currentMousePosition.X) / this.DeepZoom.ActualWidth * this.DeepZoom.ViewportWidth; newViewport.Y += (_lastMousePosition.Y - currentMousePosition.Y) / this.DeepZoom.ActualWidth * this.DeepZoom.ViewportWidth; this.DeepZoom.ViewportOrigin = newViewport; _lastDragViewportOrigin = newViewport; } // NOTE: it is important this be after the isDragging check … // since this updates last position, which is used to compare for dragging. _lastMousePosition = e.GetPosition(DeepZoom); }
In the last sections I took it slow and walked through the code to explain what we were working on. Going forward below will pick up the pace a bit, and the original code will be tweaked to get into a host control with a bit more navigation and troubleshooting advise.
this.PanRight.Click += new RoutedEventHandler(PanRight_Click); this.PanLeft.Click += new RoutedEventHandler(PanLeft_Click); this.Home.Click += new RoutedEventHandler(Home_Click); this.PanBottom.Click += new RoutedEventHandler(PanBottom_Click); this.PanTop.Click += new RoutedEventHandler(PanTop_Click);
void Pan(PanDirection direction) { double percent = PanPercent; if ( UseViewportScaleOnPan ) percent *= this.DeepZoom.ViewportWidth; switch (direction) { case PanDirection.East: this.DeepZoom.ViewportOrigin = new Point(this.DeepZoom.ViewportOrigin.X - Math.Min(percent, this.DeepZoom.ViewportOrigin.X), this.DeepZoom.ViewportOrigin.Y); break; case PanDirection.West: this.DeepZoom.ViewportOrigin = new Point(this.DeepZoom.ViewportOrigin.X + Math.Min(percent, (1.0 - this.DeepZoom.ViewportOrigin.X)), this.DeepZoom.ViewportOrigin.Y); break; case PanDirection.South : this.DeepZoom.ViewportOrigin = new Point(this.DeepZoom.ViewportOrigin.X , this.DeepZoom.ViewportOrigin.Y + Math.Min( percent, 1.0 - this.DeepZoom.ViewportOrigin.Y)); break; case PanDirection.North : this.DeepZoom.ViewportOrigin = new Point(this.DeepZoom.ViewportOrigin.X, this.DeepZoom.ViewportOrigin.Y - Math.Min( percent, this.DeepZoom.ViewportOrigin.Y)); break; } }
/// <summary> /// Zooms in around an ELEMENT Coordinate.. /// I technically did not need this function to abstract and could have called DoZoom directly /// </summary> /// <param name="p"></param> void ZoomIn( Point p ) { if (_useRelatives) DoRelativeZoom(Zoom / DefaultZoomFactor, p, ZoomDirection.In); else DoZoom(DefaultZoomFactor, p ); }
this.LayoutRoot.KeyUp += new KeyEventHandler(DeepZoom_KeyUp);
void DeepZoom_KeyUp(object sender, KeyEventArgs e) { if (e.Handled == true) return; if ( ( RunningOnWindows && ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) && (e.Key == Key.Add || (e.Key == Key.Unknown && e.PlatformKeyCode == 0xBB))) || ( RunningOnMac && ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) && (e.Key == Key.Add || (e.Key == Key.Unknown && e.PlatformKeyCode == 0x18 ))) ) { ZoomIn( _lastMousePosition); } else if ( (RunningOnWindows && ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) && (e.Key == Key.Add || (e.Key == Key.Unknown && e.PlatformKeyCode == 0xBD)))|| ( RunningOnMac && ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control) && (e.Key == Key.Add || (e.Key == Key.Unknown && e.PlatformKeyCode == 0x1B ))) ) { ZoomOut( _lastMousePosition ); } e.Handled = true; }
void PullData(ref MultiScaleImageDummyDataWrapper data, ref MultiScaleImage msi) { data.ViewportWidth = msi.ViewportWidth; data.ViewportOrigin = msi.ViewportOrigin; data.AspectRatio = msi.AspectRatio; data.UseSprings = msi.UseSprings; }
Overall I was quite impressed with DeepZoom. it is pretty cool stuff; I wish I had some cool pictures for a better application, but I did not try since I knew I could not top memorabilia.
My personal advise: do not databind to DeepZoom for now. Pull to it on Motion Finished.
No keyboard input goes into DeepZoom (since it is a FrameworkElement). In order to have keyboard input you must have a Control that has focus; since keyboard events bubble you can handle Keyboard input at a higher level (e.gl LayoutRoot, just check to see if it has not been handled previously).
On my real app –which can’t be shared as it was a customer’s app –. I ran into an issue when using Collections. My images were showing up in the wrong place. I reported it already and they are investigating –during the shower, where I do my best thinking- I came with the theory that is the resolution independence in WPF ( 96 to 72 DPI conversion). I have not confirmed.
I did not discuss collections in the post, so will try it here. Collections are cool because it gives you access to your Subimages so you can manipulate them. Move them around, scale them, animate position and Opacity. For now, beta1 has only one Collection; I think it would be cool to have multiple collections so you can aggregate. This can kind of be simulated via logic, but would be nice if it was in the control. If you simulate it, the advise I was given is do not simulate it by overlaying two MultiScaleImage controls one on top of the other, there are a few known issues with interactions on overlays (though to be honest I tried it and did not run into issues).
UseSprings= true is pretty cool, but pending how quick you want to do your panning/zooming, turning it off can make your app appear more responsive. I would not turn UseSprings off for a consumer facing app, but I would consider doing it for an internal app.. For example, I am doing a Heatmap with lots of data in it, for analytical purposes. Since it is drill through I am considering it.
When panning, make sure you handle MouseLeave on your control.
Handling mouse wheel is not available out of the box is trivial but Peter Blois has a great solution. Do not write the code to handle wheel. Peter’s code works great so far. Check his blog for updates too, he has a nice abstraction now to the same API.
If you skipped section 3, check it out. Understanding the object model is critical and takes 5 mins.
If you are writing a DeepZoom application, I recommend you use the old instantiation via silverlight.js … Click To Activate will eventually go away in IE, but in the mean time it is pretty annoying for an app that is so visual and so focused on mouse navigation.
If subscribing to MultiScaleImage.OpenImageSucceded make sure you do it from your constructor right after initializeComponent. I tried to do it of UserControl.Loaded and when doing a load on a page with image cached that is too late.
If possible try to ‘hold’ any operations until OpenImageSucceded has fired ( no pans, zooms before that). I saw weird results if I try to access properties on MultiScaleImage before this event; in particular if you access the SubImages collection before ImageOpenSucceded, then I would get an empty collection and when ImageOpenSucceeded was fired, the collection would not be overridden; so advise for collections is don’t touch SubImages before the OpenImageSucceeded fires.
Is at Skydrive
You can see it here; it is not visually impressive but I think it shows a bit of what you can do with DeepZoom and most important it is functional code you can quickly refactor and reuse. If I missed a common deepZoom task let me know. I added two extra “easter eggs” beyond the bunny and the eggs above in the walk through.
Thanks to Tim Aidlin who chose the colors and gave me cooler icons for the map; I butchered them a bit when I turned them into controls so don’t hold it against him, he is a gifted designer –you can see his real work on the MIX website and any thing else MIX08 branded.