Jaime Rodriguez
On Windows Store apps, Windows Phone, HTML and XAML

  • Jaime Rodriguez

    Announcing the "WPF for LOB" Training Tour..

    • 33 Comments

    After our m-v-vm training sold out, and most of the march trainings were filled within days, I am delighted to announce our new series of WPF trainings for April/May/June.   

    Karl Shifflett and I created our best offering to-date: two days packed with WPF, optimized for building business applications, and in your neighborhood. We are hitting five different cities this time!!

    Please join us, and please help us spread the word about the training!!

    Update on 4/2  -- Added registration tips; a lot of people were wondering about partner requirements; there are none. This training is open to every one. Sorry for asking those questions, we are tagging on existing infrastructure for registration.

    Update on 4/8Dates are now final and the good news is the events were moved up. The NY and PHX dates changed, but registration opened with right dates so any one who registered saw the right ones!

    Here is the full-invite:


    Overview
    This two day training is designed to teach developers how to create Line of Business (LOB) applications using Windows Presentation Foundation (WPF).  

    • Day One is an introduction to the WPF graphics subsystem, the tools used to build WPF applications, and the core UI services: styling, data binding, templating, layout and input-
    • The second day begins with interop (Windows Forms and Win32)  and then quickly dives into LOB topics, including building applications using the Model-View-ViewModel pattern, creating unit testable applications,  implementing data validation, and error handling.  

    After completion, attendees will have a solid understanding of WPF, its advantages over other Microsoft UI platforms, and how to use the M-V-VM pattern to create great WPF LOB applications.

    Date, Location, and Logistics

    Location

    Dates

    Click here to register (see tips below)

    Los Angeles, CA

    4/24 -4/25

    www.msregistration.com/wpflobLA

    London, UK

    5/15 -5/16

    www.msregistration.com/wpflobUK

    New York, NY

    5/29-5/30

    www.msregistration.com/wpflobNY

    Chicago, IL

    6/12-6/13

    www.msregistration.com/wpflobIL

    Phoenix, AZ

    6/5-6/6

    www.msregistration.com/wpflobAZ

    Registration tips:
    If you are not a partner or don't know if you are:
          When asked "are you registered" select No.  Select "Visiting partner" under Partner Level.
          Get creative on the Partner Type;  if in doubt, we are all "System builders"
     

    Format:
    Instructor-led training from 9 AM to 5:30 PM.   15 minute breaks every couple hours. 45 minutes lunch around mid-day.
    Food:
    Breakfast, lunch and afternoon snacks are provided.

    Cost
    The training is FREE. You do need to register prior to the event, but there is no cost.  You can register for one or two days. Registration is first-come-first serve, sign-up as early as possible to reserve your spot!


    Detailed Agenda

    • Day One:
      • Lap Around WPF
      • WPF Tools ( Blend, Visual Studio 2008)
      • Graphics Subsystem
      • Layout
      • WPF Fundamentals and new concepts
        • Application Model
        • Dependency Properties
        • Trees (logical & visual)
        • Events
        • Threading
        • Resources
      • Controls
      • Styling
      • Templating
      • Q&A with instructors at end of day
    • Day Two:
      • WPF integration with Win32 and Windows Forms
      • Data binding
      • Introduction to Model-View-ViewModel
      • Commanding in M-V-VM
      • Views, Navigation and Transitions
      • Data Validation
      • Error handling, Model dialogs, Logging
      • Unit Testing
      • MVVM & LOB tips and tricks
      • Q&A with the instructors

     

    About the instructors
    Karl Shifflett is a software architect, former Microsoft MVP, current Code Project MVP and MCAD from Bellevue, Washington. He is currently working for Microsoft on the Cider Team as a Program Manager II. He has been designing & developing business applications since 1989 and transitioned to .NET in March of 2003. In April of 2007 he joined the list of WPF and Microsoft Expression fanatics & evangelists. He is a member of Team Mole that delivered Mole Visualizer For Visual Studio to the world. He is the author to XAML Power Toys and loves WPF LOB.  Karl’s Blog: http://karlshifflett.wordpress.com/

    Jaime Rodriguez is a Senior Technical Evangelist at Microsoft. He focuses on WPF and Silverlight.   For the last four years, he has helped a lot of enterprises and ISVs adopt WPF in large scale, mission critical, projects. 
    Jaime has been doing software development for fifteen years. Prior to Microsoft, he worked at Xerox, HP, Cerner and GeoAccess.   He joined Microsoft 9 years ago, he spent the first four years as an Enterprise Architect Consultant in Microsoft Services, and the last five he has been a client evangelist covering Windows Forms, WPF and Silverlight.  Jaime’s blog is at http://blogs.msdn.com/jaimer


  • Jaime Rodriguez

    A deepzoom primer ( explained and coded)..

    • 18 Comments

    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?

    Part 1 – The History & Math behind DeepZoom

    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. 

    PYRPSD

    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).


    Part 2 – Constructing a DeepZoom Image using DeepZoom composer 

    1. We begin by downloading the "DeepZoom composer" from Microsoft Downloads.
      If you want a great reference for the tool, try the DeepZoom Composer guide. In the steps below, I am going to keep it to the minimum steps needed and some of the gotchas when using the tool.
    2. After installing the DeepZoom Composer, we launch it. 
      Trivia facts: Composer is a WPF application, like most of the other Expression products. Also, codename was Mermaid (you can see this from the export window).
    3. Under the File Menu, select "New Project"
        1. Select a location to store the project.
          I recommend some thing with a short path like c:\users\jaimer\. The composer has some usability issues that make working with long paths a little hard; and the composer will append to your path later when you export.
        2. I called it "EasterEggHunt" as that is what my project will be.
      image
    4. Now click "Add Image" to import a few images.
      You can import multiple images at once.  In my case, I am importing 3 images: bummysmall.jpg, eggs.jpg, and world2.jpg). These are in the inputimages directory if your are following along with the source.
      image 
      This added all the images we are going to use in the composer.  All images must be added at design-time.
    5. Click "Compose"  on the Center toolbar to compose the DeepZoom image.
    6. Double click the world image to add it to the 'stage' or design surface.
    7. Click Fit To Screen to maximize our space.
    8. Click on the eggs image  to add it to the stage.
    9. Zoom many times into the image at a place where you want to drop some easter eggs.
      1. Hint:  the Usual short cuts of Ctrl+ and Ctrl-  do work for zooming. Unfortunately Pan(h) and Select(v) don't work.
    10. Shrink the easter eggs into a small size -- don't worry, with DeepZoom we will be able to Zoom a lot at run-time to find them and see them.
    11. Drop the easter eggs where you want to. He is an example of mine, I dropped them in Mexico. Notice I am quite zoomed into the map and the eggs are small.

      image 

    12. Repeat the steps in 11 for the bunny picture.  In my case,  I did it in Seattle area.
      Note: unfortunately I could not figure how to drag same image twice into stage area.  The work around I used is to make a copy of the image with different name, and add it to the image gallery ( Step 4).
    13. Click Ctrl-0 to see our DeepZoom Image with out the zooms.  You sized it right if you can't easily see the eggs and bunny in the map.
    14. CLick "Export" in the main toolbar. 
    15. Here we enter the settings for output.
    16. Leave the "generate collection" unchecked for now.
      What Generate Collection does is exports the DeepZoom Image with metadata and at run-time the images can be accessed via the MultiScaleImage.SubImages  property.   If you can get to these images, you can move them around the composed image ( for layout ) you can also tweak their Opacity.
      The reason I am leaving them unchecked is beause there seems to be a bug (at least on my machine) where if I click Generate Collections my images at run-time show at an offset of where they are supposed to be.   I have reported it to the DZC team and they are investigating.
    17. Enter a Name  ( "Easter" on the export Dialog).
    18. I leave the Output path untouched.
      This is where having entered a short path in Step 2 above would pay up because their Path dialog does not Wrap and it is fairly small. [Kirupa already said this is improving for next version]. If you opt to change the path, be attentive when you export again, it seems to reset to its default value.

      image
    19. Now, assuming your output looks similar to mine above, (Create Collection unchecked) Click Export and we are done when it says Export Completed.


    Part 3 - DeepZoom Object Model

    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:

    • Logical Coordinates – is a normalized value (0 to 1) representing a coordinate in the image itself (not the control)
    • Element Coordinates – is the actual control coordinates. For example in a MultiScaleImage of Width=800, Height =400, when the mouse is at the center, the element coordinates are 400,400.  These coordinates are not normalized.

    Now, we navigate through the interesting properties and methods in MultiScaleImage

    • Source – refers to the Image source; usually info.bin when not using collections or items.bin  if using collections. 
    • SubImages – when using collections, this is a reference to all the images in a composed DeepZoom Image.
    • ViewportWidth – Specifies the width of the parts of the image to be displayed. The value is in Logical coordinates.
      For example: 
      Width=2 means image is zoomed out and only takes half the space available. 
      To zoom in, a viewport < 1 is required.  ViewportWidth of 0.5 is a 200% zoom.
    • ViewportOrigin – the Top,Left corner for the parts of the image to be displayed.  This is returned in Logical coordinates.  For example, imagine I am panning by 10% each time and I pan twice to the right while zoomed in at 100% (so no zoom), my ViewportOrigin.X will be 0.2.
    • UseSprings – gets or set whether DeepZoom animates the transitions ( like ZoomAboutLogicalPoint, updates to ViewportOrigin, etc. ).

    The interesting methods are:

    • ElementToLogicalPoint – takes a coordinate of the control, and gives you a logical ( normalized coordinate).
      For example, mouse at Center (400,400) with ViewportWidth=1 and you call ElementToLogical ( ) will return (0.5, 0.5)
    • LogicalToElementPoint – takes a logical coordinate (normalized) and returns a point in the MultiScaleImage control where that logical point corresponds to.
    • ZoomAboutLogicalPoint – implements the Zoom.  The two parameters are the new zoom multiplier - as an increment from current zoom factor in the image - and the Logical point at which to zoom around. 
      Example of the incremental zoom would be to ZoomAboutLogicalPoint  ( 1.5, 0.5, 0.5) .. I will be zoomed in to 1.5 times;  if I repeat this operation with same values I am zoomed in at 1.5 * 1.5  which is 2.25 times from size where I started.

    In my opinion, surprisingly missing from the API were:

    • The original width and height of the DeepZoomImage  (so that I can translate normalized logical coords to physical on the image).
    • Zoom – to tell you the current total Zoom level; this one you can get around by keeping track of any zooms you implement. Another possible workaround is that Zoom appears to be 1/ViewportWidth; I can’t think of the scenario where this does not hold, if there is please let me know and again just keep track of your zooms if that happens.

    Part 4 – Coding a  DeepZoom Host User Control

                         

    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]

    1. Inside Visual Studio 2008, create a new Silverlight Application; I called it DeepZoomSample.
    2. Build the application so the Clientbin directory is created.
    3. Copy the output from the DeepZoom Composer to the Clientbin directory of our Silverlight application.
      In my case, I called the output “Easter” so I can go into the output directory from composer and just copy that whole directory to my Silverlight Application’s ClientBin.
    4. Now that we have our image, we can edit the XAML in Page.Xaml, to show the image.
      <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>
      If you run the application now, you will see the image loads  but there is no functionality: zoom and pan have not been implemented. 
      For zoom, we need to use the mouse wheel, but Silverlight has no native support for it. A good work around is to use Peter Blois’ MouseWheelHelper. This class uses HTML Bridge to listen to the mouse wheel event in the browser and exposes the events to managed code.
    5. Add a new code file to your project, I called it MouseWheelHelper.
    6. Copy Peter’s code below into the MouseWheelHelper file.

      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();
                      }
                  }
              }
          }
      }




      MouseWheelHelper fires a Moved Event whenever the Wheel moves. The EventArgs is a MouseWheelEventArgs, which has the delta property. Delta is a normalized property (0 to 1), for now all we look at is whether it is greater than 0 or not.
      If Delta is greater than 0, then the wheel has rotated away from the user; if Delta is a negative number, then the wheel has rotated toward the user.

    7. Before we handle the Moved event, let’s add a ZoomFactor property to our control, this will be the increment/decrement on a wheel operation. The default value is 1.3, which is a 30% increment.  Nothing scientific behind this number, I am pretty much just ‘copying’ what I see every other sample do. I think the number works OK.
       protected double _defaultZoom = 1.3; 
             public double DefaultZoomFactor
             {
                 get
                 {
                     return _defaultZoom; 
                 }
                 set
                 {
                     _defaultZoom = value; 
                 } 
             }
    8. We also add a CurrentTotalZoom property, this will be a cached version of overall zoom level (since we can’t query this from the MultiScaleImage API.  I also added a MaxZoomIn and MaxZoomOut to prevent the image from going too far in (is there such a thing?) or too far out. Too Far out did matter as the image can disapper if you go too far.  In my case I picked my Maximum values arbitrarily.


      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; }
             }


    9. Now, we can add a DoZoom function to our class, this will be called when there is a Zoom operation.   The parameters for it are: the new Zoom level RELATIVE to where the image is at,  and  a point in Element Coordinates since most likely we will be zooming around the mouse, and we get Element coordiantes out of that.


      /// <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; 
                      } 
    10. Now we are ready to handle the MouseWheelHelper.Moved event.   We will do it in three parts: 
      1. We will subscribe to MouseMove event in the MultiScaleImage, so we can keep track of where the mouse is; we need this because MouseWheelHelper.Moved does not give us a MousePosition, and there is no way to query MousePosition in Silverlight2 outside of a Mouse EventHandler.

        // inside the Loaded event for the user control
        //{
        DeepZoom.MouseMove += newMouseEventHandler(DeepZoom_MouseMove);
        _lastMousePosition = new Point ( DeepZoom.ActualWidth /2 , DeepZoom.ActualHeight /2);
        //}
         
        protected Point _lastMousePosition;

        void DeepZoom_MouseMove(objectsender, MouseEventArgs e)
        {
                 _lastMousePosition = e.GetPosition(DeepZoom);
        }
      2. Now we instantiate a MouseWheelHelper and subscribe to Moved event

        // inside the Loaded Event for UserControl
        MouseWheelHelper mousewheelhelper = new MouseWheelHelper(this);
        mousewheelhelper.Moved += newEventHandler<MouseWheelEventArgs>(OnMouseWheelMoved);
                 
      3. We add the OnMouseWheelMoved function to the UserControl class..
        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);
           } 
        }

      4. NOTE: If you compare the source above with the code in the sample source, they are slightly different.
        In the sample source there is two approaches to handling Zoom, and there is a boolean flag called _useRelatives that controls this. if you set _useRelatives to true, it will zoom based in relation to a last zoom; I think this makes it more complicated but for some reason most samples I have seen of DeepZoom use this calculation.  I think the behavior is the same than the approach I took, but the math is simpler with the approach in the steps above.   I did add both in case I find later that there was a scenario addressed by the _useRelatives approach.

    11. At this point we should be able to run the application and get Zoom to work (in and out) around the mouse location.  Compile the app and run it to make sure we are making progress.
    12. To Pan, we need to detect the MouseLeftButtonDown and MouseLeftButtonUp,  the assumption is we will pan when the mouse is down, and pan in the direction of the Mouse movement and then stop panning when the mouse is up.
      1. Let’s add a handler for MouseLeftButtonDown, we add the listener in the UserControl’s Loaded event.  This handler will set a variable called _isDragging  to flag that the mouse is down; we will use this flag on the MouseMove handler.

        // inside the Loaded function, we add code behind our MouseWheelHelper code added earlier..
        DeepZoom.MouseLeftButtonDown += newMouseButtonEventHandler(DeepZoom_MouseLeftButtonDown);
      2. The handler looks like this:
        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; 
                   
        }
      3. Now we subscribe to MouseLeftButtonUp, inside Loaded function  and we add the handler function for it.

        //  The one liner below goes in the Page_Loaded event handler
        DeepZoom.MouseLeftButtonUp += newMouseButtonEventHandler(DeepZoom_MouseLeftButtonUp);


        void DeepZoom_MouseLeftButtonUp(objectsender, MouseButtonEventArgs e)
        {
            this._isDragging = false;
        }
      4. Now we tweak the code inside MouseMove  to Change the ViewportOrigin to perform the Pan operation.
        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); }
      5. We should also detect the MouseLeave event, and if we are in the middle of a Pan, we need to reset the _isDragging flag.

        // inside the UserControl Loaded handler DeepZoom.MouseLeave += newMouseEventHandler(DeepZoom_MouseLeave);
        void
        DeepZoom_MouseLeave(objectsender, MouseEventArgs e)
        {
        this._isDragging = false;
                  this.DeepZoom.Cursor = Cursors.Arrow; 
        }


    13.   That is it for the basics and the ‘hard stuff’ … with not too many lines of code, we have Zoom & Pan in our host.  Along the way we added a few properties we can reuse to create UI around our DeepZoom image.

    Part 4.1 Adding more UI to navigate in a DeepZoom Control.

    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. 

    We begin by adding a Navigation wheel to the UserControl.xaml.  The wheel has four repeat buttons with arrows pointing east,west,north, south; these buttons will be used to pan in the respective direction.

    At the center of the wheel, there is a regular button, which takes you home ( to where there is no Zoom, no panning, etc. )

    image
    1. Implementing Panning is done by changing the viewportOrigin. We have a choice of Panning relative to control size and relative to ImageSize. Let me explain:

      If the controls ViewPortWidth is 1.0 – and we pan by a Logical increment of 0.1  we are panning 10 percent on a direction. This % seems reasonable.
      If however we are zoomed in 500% ( viewportWidth = 0.2 ) and we do a Pan of 0.1 (logical)  then we are going to pan by a lot ( 50% of what is visible).  So we need to scale our original 0.1 increment by the ViewportWidth.  Don’t you think?

      Here is what I did:
      1. Added a Property of type double called  PanPercent.   This property holds the increment. You can set it from XAML; default is 0.1  ( aka 10% )
      2. Added a property of type bool called UseViewportScaleOnPan.  If this is true, we will pan by  PanPercent * ViewportWidth; if this is false we pan by PanPercent.
    2. Now we are ready for Panning. We add event handlers for all our Pan RepeatButtons:
      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);


    3. Each of the event handlers calls the Pan function with their respective direction:
      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; 
           } 
      }

    4. Panning to Home is a combination of setting the ViewportOrigin to 0,0 and setting the ViewportWidth to 1

      void Home_Click(object sender, RoutedEventArgs e) {
      this.DeepZoom.ViewportOrigin = new Point(0, 0);
      this.DeepZoom.ViewportWidth = 1; }

    5. Next thing is to implement Zoom in and Zoom Out; these are also trivial, the only decision to make is where to Zoom, I needed two approaches:
      When Zooming using keyboard,  Ctrl+ Ctrl- (on Windows)  I wanted to Zoom at the mousePosition. 
      When zooming using the magnifying glass icons I added to the UI, I can not use the mousePosition – as I knew the mouse was where the magnifying glass – so I zoomed around the center of the control.

      Let’s begin with simple ZoomIn using Click from magnifying glass:

      void
      btnZoomIn_Click(objectsender, RoutedEventArgs e)
      {
                 ZoomIn( newPoint( DeepZoom.ActualWidth / 2 , DeepZoom.ActualHeight / 2)); 
      }
      /// <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 );
      } 
    6. When zooming from Keyboard.  The “gotcha” was that the DeepZoomImage is a FrameworkElement and it does not receive focus (in Silverlight Focus is at Control class), so what I did was listen for Keyboard event in the Grid that is the top container in my Host UserControl. 
      this.LayoutRoot.KeyUp += new KeyEventHandler(DeepZoom_KeyUp);
    7. Another surprise was that + (by pressing Shift+9) on my machine turned out to be a keycode, not a standard key.  I am not a keyboard expert in Silverlight (yet) but from what I read, keycodes can be different across platform so I added code to check in case I am running in the Mac. I checked using the Environment.OSVersion property.     Please double check this code, as I was pretty lazy about what running on Windows or Mac means. 

    8. public bool RunningOnWindows
              {

                  get
                
      {
                      return(
                          Environment.OSVersion.Platform == PlatformID.Win32NT ||
                          Environment.OSVersion.Platform == PlatformID.Win32S); 
                     
                  }
              }
    9. Now we do the Zoom, note that I am not too confident on my Mac KeyCodes; I got this by sniffing on my Mini but I have a regular,ergonomic Microsoft keyboard on  that mini so double check with your keyboard just in case.
      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;
       }
    10. At  that point our app would be functionally complete but I want to share a few more findings from my learning so let me share  the DebugSpew, it can be handy for you too.
      1. When I wrote my first deepZoom app, I took the usual approach of databinding to it so I can reverse engineer it and found a few issues; since I am traveling I have not discussed them w/ DeepZoom folks in depth for now take these as “gotchas in beta1” and will try to get some one from DeepZoom team to confirm if these are ‘final’ behaviors (feel free to leave feedback here or at the expression blog) letting them know your preferences.
        • Databinding to MultiScaleImage was a bit flaky.   ViewportWidth and ViewportOrigin did not fire notifications for me.  The explanation I have seen is that  because DeepZoom animates with springs, binding to these properties was not recommended.  These values will change every frame during a transition.   The recommended workaround was to subscribe to the MotionFinished event.  This fires at the end of a transition, so gives me a nice way to pull the value.  In my case (for debug/learning deepZoom), the workaround  was very acceptable so I implemented it.

          // in my loaded event for the page
          DeepZoom.MotionFinished += newRoutedEventHandler(DeepZoom_MotionFinished);



          void DeepZoom_MotionFinished(objectsender, RoutedEventArgs e)
          {
             if(DebugSpew.DataContext != null)
             {
                   MultiScaleImageDummyDataWrapper dw = DebugSpew.DataContext asMultiScaleImageDummyDataWrapper;
                   if(dw != null)
                   {
                            PullData(refdw, refDeepZoom);
                            MouseLogicalPosition = DeepZoom.ElementToLogicalPoint(_lastMousePosition);
                     } 

                  } 

            }
          void PullData(ref MultiScaleImageDummyDataWrapper data, ref MultiScaleImage msi)
          {
                      data.ViewportWidth = msi.ViewportWidth;
                      data.ViewportOrigin = msi.ViewportOrigin;
                      data.AspectRatio = msi.AspectRatio;
                      data.UseSprings = msi.UseSprings; 
                       
          } 
        • Databinding to the other properties (that are not animated per frame) in MultiScaleImage also gave me a bit of trouble [some times the control would not show up]. My advise is to not data bind for now.   
        • Once I had the databinding worked out, I added a handler to pull data from the MouseMove so I could show coordinates when Mouse is moving, I wanted them in logical and element coordinates, so I did the translation and I added an extra  call from MotionFinished to translate the point again as the logical Coordinate changes when the Viewport changes.

    Part 5 – Lessons learned

    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.

    1. My personal advise:  do not databind to DeepZoom for now. Pull to it on Motion Finished.

    2. 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).

    3. 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.

    4. 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).

    5. 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.

    6. When panning, make sure you handle MouseLeave on your control.

    7. 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.

    8. If you skipped section 3, check it out. Understanding the object model is critical and takes 5 mins.

    9. 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. 

    10. 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.

    11. 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.

    Part 6 – Source

    Is at Skydrive

    Part 7 – Show me the app.

    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.

    1. One is for  the NCAA basketball team for my college, which won yesterday and made it to the Final Four tournament .  (Hint, the four finalists are:  Kansas, Memphis, UCLA and North Carolina.
    2. The other one is a bitmap with dates & locations for my upcoming Europe trip (Germany, Austria)..  If you are in the area and available the nights I am in the area, ping me and we can get together to discuss any thing .NET client related (e.g. WPF, Silverlight, and ASPX).

    Part 8 -- Thanks

    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.

  • Jaime Rodriguez

    Microsoft Client Continuum in action: The Silverlight toolkit charts, running in WPF

    • 17 Comments

    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)..

    WPFSLCharts

  • Jaime Rodriguez

    List of features to track in WPF4 and the details on beta1

    • 15 Comments

    I am late to this  but still wanted to share an “insider view” into the status of “WPF 4” in the recently released .NET Framework 4 and VS 2010  beta1.


    Status of the run-time (on the public beta 1).
    The WPF team cranked hard until last minute on 3.5 SP1 so they have been playing catch-up to .NET 4 and VS2010.  The beta does not really do justice to the progress the team is making towards RTM. In a few cases, the team ‘compromised’ not shipping the feature in beta1 to optimize for the RTM schedule (preparing a beta does take integration work and testing, plus they had to put the longer/harder features up-front to decrease ship risks). 
    Beta2 appears to be in good shape for you to preview and share feedback still before .NET 4 RTMs.  

    For those looking into beta1, I would say the two areas where we need feedback will be:

    • The new XAML stuff (in particular we want to make sure we do not break you)
    • The tools ( Cider designer);  I would love to hear what people think of the new features. 

    Want to learn more about beta1. Here is a bit of the action:

    • Video Interview with Ian Ellison Taylor WPF and Silverlight GM, and Kevin Gjerstad, WPF Group Program Manager.  It pretty much discusses what is in and out (for beta1 and RTM).
    • Rob Relyea recorded a video on the XAML enhancements.  This is a must watch and then follow it with this post and any other recent posts in Rob’s blog.
    • Jossef Goldberg has a great post on client profile in .NET4.  We recorded a video, to be posted in the next few days; look for it here via update to this post, or in the continuum show.
    • Tomorrow we are shooting the Touch video;  ironically they reached feature complete in beta2 today, maybe we can get a peek; video should be posted early next week. 
      [If you want to know why I am posting today instead of next week, we are doing LOB Tour in NY this weekend, so I am out on the road again. I hope you understand, post will be updated as we go]
    • WindowsClient.net now has a new section for the Cider designer. Karl Shifflett and the Cider crew will be putting videos there as they go. For starters, Mark Wilson-Thomas has a good “Lap Around Cider in beta1 .  


    On the way to .NET 4.
    Here is my view into the new features I am tracking and current (best guess) estimate on timelines. The list is not all inclusive, so let me know if I missed one you cared about.  note that this list focuses on new stuff;  I did not include all the bug fixes and minor improvements to existing stuff.

    Huge WARNING!!  None of the below features are guaranteed.  Nothing is official until we RTM. 
    Please do not make assumptions or plans based on this list.

    Category

    Feature

    In beta 1

    Expected by  beta 2

    Windows 7 light-up

         
    Multi-Touch Basic manipulation and inertia.
    Raw touch events are not in.
    Yes.  Beta1 + Raw touch.  A few tiny breaking changes.
      Taskbar integration Not in beta 1 Yes
      Vista + Win7 Native Dialogs Yes Same as beta1
      Ribbon (though it is not win7 specific at all). Not in beta 1 Ribbon will be shipping out of band still.  Next preview ships after beta2. It will RTM by .NET 4 RTM or earlier.
        Graphics& Text      
      Text enhancements Not in beta 1 Yes
      Cached Composition Not in beta1 Yes
      Support for Pixel Shaders written w/ HLSL 3.0 Not in beta 1 Yes
      Caret customization Not in beta 1 Yes
      ClearTypeHint Yes Same as beta1
    Legacy effects get cut Yes. Old legacy Bitmapeffects are a no-op, unless it is one of the effects that are hardware accelerated. Same as beta1
    Fundamentals & AppModel      
      XAML 2009 Beta1 Yes, minor changes.
      LayoutRounding Not in beta1 Yes
      Bindable Run Not in beta1 Yes
      Visual State Manager Not in beta1. In by beta2, now shipping in the framework.
      Animation Easing functions Yes Same as beta 1
      Datagrid Yes. Moved from toolkit to framework.  Minor bug fixes. Same as beta 1.
    DatePicker Move from toolkit to framework. Minor fixes. Same as beta1.
      Full Trust XBAP Not in beta1 Yes
      Databinding improvements Not in beta1. Yes
         Deployment
    Client Profile New client profile is in, but no web bootstrapper. Still needs to be compressed (for size). Yes, full functionality.
      Client Profile Configurator No. Will ship out of band. No. Will ship out of band.
              Tools      
      VS2010  Cider Cider has a lot of improvements.
    Perf work is not done.
    Yes.
    VS2010 new Editor Since the text enhancements are not in the run-time, the editor does not get them. Yes.
           


    Note: At PDC, we said that DeepZoom would be in WPF4. Unfortunately that feature has been cut. We just could not squeeze it into the schedule.  There are workarounds to it: you can host Silverlight inWPF using web browser control or using the Silverlight hosting APIs.
     

    Details on  the features:
    Below are high level descriptions around the features listed above. Must share that Ted Hu created the descriptions for a different document and I am shamelessly plagiarizing it. Thanks Ted.

    Multi-touch
    Windows 7 is introducing multi-touch input and manipulation processing, which Windows Presentation Foundation exposes. Multiple finger input will be exposed through existing and new input events, while new manipulation and inertia events will be exposed for developers to consume.

    • Multi-touch Manipulation and Inertia (Pan, Zoom, Rotate) events on UIElement
    • Raw Multi-touch events (Up, Move, Down) on UIElement, UIElement3D and ContentElement
    • Multiple capture supporting multiple active controls
    • ScrollViewer enhancement to support multi-touch pannning
    • Touch device extensibility
    • Future Surface SDK compatibility

    Windows 7 Shell Integration
    WPF 4 exposes several new Windows 7 Shell features to WPF developers These Shell features empower application authors to provide a richer, integrated user experience. The new taskbar is less cluttered and can convey more information at a glance. The Aero thumbnails support user commands. Jump lists provide access to contextual startup tasks and files available to the application.
    WPF 4 will integrate Windows 7 JumpList functionality, including:

    • Tasks
    • Items
    • Recent and Frequent Lists integration
    • Custom Categories

    Taskbar integration including:

    • Progress bar
    • Overlay Icon
    • Thumbnail buttons with commanding support
    • Description Text
    • DWM Thumbnail clipping

    Text enhancements
    WPF 4.0 Beta 2 introduces a new text rendering stack which allows for much clearer text rendering.  This change adds the necessary knobs to allow WPF text to look virtually indistinguishable from Windows’ traditional GDI-rendered text.  The clarity improvements also dramatically improve readability for many East Asian languages.

     

     


    ClearType hint
    ClearTypeHint allows app authors to enable cleartype when rendering text to IntermediateRenderTargets, where it would normally be disabled by the system.  The system normally disables cleartype on IRTs because Cleartype only works properly when it’s rendered to an opaque surface, and it’s extremely difficult for the system to determine if an IRT will be rendered with transparency.  By Setting this to true, you’re basically letting the system know that the IRT is transparent.  This means you can now enable cleartype with layered windows, in Visual/Drawing brushes, in Effects, and in 3D if you so desired it. 

    Full-Trust XBAP
    Starting in WPF 4.0, the ClickOnce elevation prompt is enabled for XAML Browser Applications (XBAPs) in Intranet and Trusted Zones, making it easier to deploy full-trust XBAPs. For XBAPs that require security permissions greater than the minimum code access security (CAS) permission grantset of the Intranet and Trusted Zones, the user will be able to click 'Run' on the ClickOnce elevation prompt when they navigate to the XBAP to allow the XBAP to run with the requested permissions.

    LayoutRounding
    WPF 4.0 will adopt the LayoutRounding property, originally introduced in Silverlight2. WPF’s layout engine frequently calculates sub-pixel positioning coordinates. This can lead to rendering artifacts as elements positioned on sub-pixel boundaries are antialiased over multiple physical pixels. LayoutRounding will force the layout engine to place elements on whole pixel boundaries, thus removing most of the rendering artifacts caused by this problem.

    Bindable Run
    The Run class has had a simple CLR property Text since the first release of the framework. In WPF 4.0 we have backed Run.Text with a dependency property. Among the many benefits of being a DP, Run.Text now supports data binding. One-way binding (from data source to Run.Text) is fully supported, and two-way binding is supported as long as the Run is not modified via the Text OM or from a RichTextBox.

    Selection and caret customization
    Simple app customization is a great WPF strength. In the past, the selection color and caret color for text input and reading controls (TextBox, RichTextBox, FlowDocumentReader, etc...) has been hard coded. WPF 4.0 will introduce the SelectionBrush and CaretBrush properties. These properties will allow developers to control the brush used to draw both selection and carets in many WPF controls.

    Animation Easing functions
    Is the exact same functions we announced in Silverlight 3;  I can not tell you who created them first; funny how schedules work; but really nice to have compatibility.

    BindingExpressionBase.ValidateWithoutUpdate does for a single binding expression what BindingGroup.ValidateWithoutUpdate does for a binding group – namely it starts the validate/update process but stops just before writing a value back to the source item

    When BindingGroup.SharesProposedValues is true, the binding group will keep track of proposed values even when the UI elements that hold them are replaced.  This supports a UI design pattern used by DataGrid (for example), where only one source property is presented with an “editing template” (containing editable controls – TextBox) while the other properties are presented with “display templates” (containing non-editable controls – TextBlock).   As the user focuses a new property, its display template is replaced by an editing template, while the previously focused property’s editing template is replaced by a display template.  The BindingGroup now manages the proposed value (if any) held by the old editing template, so that it can be displayed (if possible) in the display template and written back to the source when the user commits the binding group

    LayoutRounding
    Speaks for the feature already in Silverlight, where the layout system rounds any non-integral values at the time of a layout pass. This helps prevent the sub-pixel anti-aliasing effects that people describe as "blurriness"

  • Jaime Rodriguez

    Silverlight in Financials Demonstrator

    • 15 Comments

     

    Yesterday at the Financial Services Developers Conference in NY,   Marley Gray & Joe Cleaver showed  the "Silverlight in Financials Demonstrator".

    The demonstrator is a 'mock-up' website that shows the interactions and experience you could get from a silverlight enabled site. Features like interactive video that drives charts, client-side charting, drag & drop, client-side calculations (for responsiveness), cross-domain web services calling, etc. come together seamlessly to create a slightly different banking experience from what we see today.    

    If you have Silverlight 2, you can play with the demonstrator at this site

    You can also see an internal (=not polished) recording of  a walk through of the demonstrator from this silverlight streaming video.

    If you want a script that helps you walk through the interactions, you can find one here.

    finShot

     

    The plan and the source:

    Our goal from the beginning has been to release the source code at devcon; since that was yesterday, the source is available today from here.

    Warning: the demonstrator grew quickly from a Silverlight 1.1 app with three scenarios to a SL 2.0 application with  six or seven scenarios; so we are a bit behind on cleaning up the code. The plan is to do a bit more clean up over the next few days ( or say all of next week)  and then put it out at Silverlight.net gallery when the code cleanup churn decreases.  Please check the SL gallery or check this blog in a week or so for an updated source.  [It takes that long because we do it one hour at a time on evenings or when I have free time at work (which is not much)].

    Known issues:
    Apologies in advance to those outside the US or not running an en english locale. We are aware some of the ' banking concepts' might not apply but we hope some of the interactions are generic enough that are useful to show.  We have also not localized the site.  Please do report bugs around localization. 

    Credits:
    Infusion development did most of the coding. Joe Cleaver, Marley Gray, Joe Rubino and a few others in the MS financials team helped define the scenarios.   

  • Jaime Rodriguez

    M-V-VM training day sample application and decks

    • 14 Comments

    During the M-V-VM training (last saturday) with Karl Shifflett I showed and referred often to this Southridge application below..
    The app is not very clean(when it comes to resources and styles in particular) but it is a fairly good ViewModel example, and it served well to illustrate a few of the points we made at the training (both good and bad practices).  So I am sharing it (as promised).   

    The application consists of three views sharing one ViewModel around real-estate data (called MainViewModel).

    The application's chrome includes a ribbon that drives all the Viewmodel via its filtering functionality.  The two filter commands that work are Bathrooms and bedrooms [if you change the selection for these, the results on the views should filter].

    image 

    I purposedly did not make the main window with the ribbon a view because I often run into applications that transition views so I wanted to show the concept of having a constant element in the chrome.   The ribbon is functional ( you can see its resize behavior).
    The checkbox columns are bound to the MainViewModel, and drive the columns that show up in the Search view (below).

    The views
    Again, the views are the results for the search from the criteria in the ribbon. 

    image image

    Both of these views are 100% XAML. 

    The grid is very functional, sort, selection, etc.   I call it SearchView.
    The map is simply a Listbox with a datatemplate and a tooltip on the ListBoxItems. I need to add more to that UI.  I call it the MapView :)
    You navigate to the map view, by either clicking on the map icon on the left hand side of any row of the data grid, or by clicking on the Map Ribbon button on the Search Results tab. 
    The full details of course include an OpenMap Command exposed in the MainViewModel that does the work.

    To go back to SearchView (from any view), just click on the Search tabs, and it automatically goes back.  The way that happens is through an attached behavior in the RibbonTab:

    <r:RibbonTab Name="SearchTab" Label="Search Criteria" view:RibbonTabSelectedBehavior.SelectionChanged="{Binding SearchSelectedCommand}" 

    This is a standard attached behavior that delegates the event and 'forwards' it the SearchSelectedCommand on the MainViewModel.  You can see the implementation on the RibbonTabSelectedBehavior class.


    There is another totally different View, called Profile.. 

    image

    This view demonstrates data validation using IDataErrorInfo. It has an InfoTextBox (from Kevin Moore's bag-o-tricks) and shows the fields that are invalid in red.

    The validation is trivial, mostly checks for empty strings on all fields, except zipcode, around ZipCode it enforces the 5 digits or 9 digits with a dash format for US Zipcodes.

    [For details, check the Profile class] in XMLData project. ]

    You can see how the "Save Profile" command is wired to only be enabled when the data is valid.

    The Profile view purposedly contains a "PreferencesEditor" usercontrol that is not a view itself; it takes data context and drives its UI from it, but does not have full view functionality (again to illustrate a point: not every thing is a view).

    The profile window has both a DataTemplate ( for everything in "Contact information"). 

    The listboxes in Location are interesting because they State is populated with read-only data (I call it meta-data) that is not coming from the view model,  but the views are bound to the Viewmodel when selection happens (county is driven by the selection on State).   Be aware that the data is not big and some states (e.g. CA) might not have valid data. Try WA to see it the way I see it.

     

    The final screen is the Listing Details screen. This is a modal window that shows Listing details. Nothing too exciting here but it is a wired view model, including the Make appointment button. You may notice that the "Neighborhood" data template in this view is the exact same data template from the Tooltip on the map.   yes, I am lazy and I drive reuse, that is why viewmodel is my friend.

    The Listbox has a very "view specific" behavior implemented by 3 lines of code behind, it is used to implement selection when mouse enteimagers a listboxitem. I purposedly left it as is, though I am sure some M-V-VM purist will tell me I should have implemented it as an attached behavior. I chose not to because I did not want the viewmodel to manipulate the view on a quirk like this one. The view can self-contain the behavior and yes, if I try to move the code to Silverlight it will need the same 3 liner. I was OK with that.  I felt it was equally bad to have the ViewModel be listening for view events and "manipulate the view" directly (which I would have needed).

    At last, there is one class that keeps it all together, I called it the ViewManager. This is an over-simplistic implementation of a Presenter for the views that handles transitions across the views.  The views register with the viewmanager as they are instantiated, and the ViewModel can trigger call into the ViewManager ( a singleton) of course to trigger transitions across views. The viewManager itself can have a ViewModel if needed; in this case I did not use it, but in other apps I have used it.

    That is it, if you were at the class I hope you remember this.  If you were not, then maybe looking at the deck might help, though I must say the class was quite driven by example, so the slides are a bit presentation-zen-like. Please try the notes for a bit more context and drop me a comment or email if that is not cutting it.

    At last, thanks to all those that attended the class.  It was a lot of fun, and I really enjoyed meeting all of you.
    Thanks also to Matthias & Bruno & Karl for puttting it together and for inviting me. 

    Presentation (ppt deck) is here.   Code is here.  The code requires the WPF toolkit, all assemblies needed should be included in the project but if you get a compilation error, try getting the toolkit and the ribbon from codeplex

    [Disclaimer again, the code needs some heavy scrubbing on the UI and resources; also I had to take out the data that I normally use, so if you see #if SQLCE, simply ignore these.  I replaced all the data with an XML file (for easy tweaking). It is not real data, so don't be surprised when you see a Seatttle address in an Issaquah neighborhood.  I merely invented the data on the fly].

  • Jaime Rodriguez

    XAML guidelines part 3

    • 12 Comments

    There were 3 goals to the XAML guidelines series:

    1. Show that there is not a single right answer (that is why we interviewed multiple people, multiple projects)
    2. Document common practices and previous experiences that worked.
    3. Inspire others to share their own opinions and practices.

    In order to accomplish parts 2 and 3, I have typed a few of my recommended best practices for any one starting new..    

    Since not every one will agree with my opinions so I am labeling this a draft, I would love for people to chime in with better suggestions and comments agreeing or disagreeing with the ones I listed.  Feel free to leave comments, or write your own blog post and link to it from here.  You can also drop me an email directly via contact page.  

    I am planning to leave this open for at least a week and then try to update the document to go beyond draft.

    XAML Guidelines Draft  HTML, PDF, XPS and DOCX.

    For any one who finds this does not cover every thing they were wondering, please do let me know what I missed. I have at least two more topics that are related that I want to tackle next, but I figure partitioning them works best so we can build on a foundation.

    Happy coding!

  • Jaime Rodriguez

    Announcement: WPF Pixel Shader Effects Library on codeplex..

    • 11 Comments

    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]..

    • Effects: BandedSwirl, Bloom, BrightExtract, ColorKeyAlpha, ColorTone, ContrastAdjust, DirectionalBlur, Embossed, Gloom, GrowablePoissonDiskEffect, InvertColor, LightStreak, Magnify, Monochrome, Pinch, Pixelate, Ripple, Sharpen, SmoothMagnify, Swirl, Tone, Toon, and ZoomBlur…

      Here are samples of the effects in action.

    Original content (and type) RenderTargetBitmap of content with Effects applied (via test app).
    None Swirl Embossed
    (Image) Swirl Embossed
    NoEffect InvertColor Pixelate2
    (Vectors) InvertColor Pixelated

    • Transition Effects:
      BandedSwirl, Blinds, Blood, CircleReveal, CircleStretch, CircularBlur, CloudReveal, Cloudy, Crumble, Dissolve, DropFade, Fade, LeastBright, LineReveal, MostBright, PixelateIn, PixelateOut, Pixelate, RadialBlur, RadialWiggle, RandomCircleReveal, Ripple, Rotate, Saturate, Shrink, SlideIn, SmoothSwirl, Swirl, Water, Wave..

      To see these in action, you really should check out the demo video on channel9 or go ahead and get the source from codeplex
      I promise it will be fun... [in a geeky kinda way].

    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!

  • Jaime Rodriguez

    dabbling around the new WPF datagrid (part 1)

    • 10 Comments

    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.

    • Part 1 (this write-up) is about the features in the grid and the ones missing from it.
    • Part 2 is a hands-on exercise to apply the features to customize the presentation of data (aka view) of the datagrid.
    • Part 3 includes a few tips & tricks on customizing/styling the datagrid.

    The source for this sample is here.

    Getting Started  was trivial. 

    1. I got the bits from codeplex,
    2. created a new WPF application, and
    3. added a reference to the WPFToolkit.dll.
    4. From my Window1.xaml, I added the xmlns declaration so I could refer to the datagrid. No need to map the assembly, the tools do that for you.

    <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':

    DataGrid0

    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:

    DataGrid1

    [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:

    • Cell - selects cells.  In this mode, SelectedCells property has selected cells and SelectedItems is null.
    • FullRow-- when a user selects a cell in a row, all of the cells in that row are selected. In this mode SelectedCells has all the selected cells in that row. SelectedItems has the selected rows.
    • CellOrRowHeader - a mix from the two above, where full-row select happens when clicking on the RowHeader (if its showing).  In this mode:

        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:

    Data Type Generated Column
    string DataGridTextColumn
    Uri DataGridHyperlinkColumn
    bool DataGridCheckBoxColumn
    enum DataGridComboBoxColumn*

    *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:

    • Using the Keyboard – cell has focus, start typing, goes into edit
    • Using the Keyboard – cell has focus, enter edit mode command (ex. F2), goes into edit
    • Using the Mouse – cell has focus, click, goes into edit
    • Using the Mouse – cell may or may not have focus, double click goes into edit.

    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:

    • BeginEditCommand (any of the gestures above)
    • CancelEditCommand  ( press Esc )
    • CommitEditCommand  (press Enter, Tab to next cell, or change focus using mouse)

    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.

  • Jaime Rodriguez

    Client profile explained..

    • 10 Comments

    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.  

    • Why only install on 'clean' machines?  If you have the full 2.0 framework, and we installed a subset of that, it that could break your 2.0 apps already installed.
    • If you think further into this, Vista shipped with the .NET framework 3.0 in the box, so that gets Vista out of the way and we conclude that client profile is supported on XP machines that do not have a framework installed at all.  For WS03, since it is server, we don't do client profile.
    • On any machine that is not XP or that already has a framework, when you try to install client profile, it will install the full .NET 3.5 SP1.  

    If you see above again, step #3 is upgrading the framework from client profile to full .NET 3.5 SP1.. how does it happen?

    • Once you install client profile, the next time Windows Update runs on that system, it will try to upgrade it to full .NET 3.5 SP1. This is nice because the download and install happens on the background (for me windows update runs at 3 am every day).
    • There is likely a brief period of time when a machine between client profile getting installed and Windows Update upgrading it to a full 3.5 SP1. What happens during this period?
      • Any apps coded against 3.5 SP1 client profile will run fine.
      • Any apps that need the full framework, will prompt for a full framework install when they are launched.  You will be able to install full-framework seamlessly you just won't get the benefit of this happening on the background (if you used Windows Update).

    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: 

    • The bootstrapper for client profile is a tiny exe ( < 300K). It should be used for applications that will be online when you install. The bootstrapper looks at your OS and hardware platform ( e.g. x86) and only downloads the needed bits (~28 MB). This is online scenario is what client profile is designed for.
    • The client profile off-line installer. Most people would think this is a 28 MB file, would not you? 
      Well, unfortunately not. It is actually ~250MB. The reason for that is we assume you will be off-line and your application can't fail at install time, so we include the whole 3.5 SP1 package plus the 28 MB client profile, this way if you try to install it on a machine that already has the framework, we can still install 3.5 and have your application will run. 
      • My personal 2c here is that most people who need to install off-line would be better off installing the full 3.5 SP1 framework at once. It is slightly smaller package and most importantly gets it all out of the way, so there is no WU upgrade later; the only gotcha is that the installer will take a bit longer to install than using client profile.

    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.

  • Jaime Rodriguez

    Working with Collections in Deep Zoom.

    • 9 Comments

    In the Deep Zoom run-time, you can load two types of compositions:

    • A single image composition is when you interact with a single image at run-time.  This does not mean you started with a single image, you could start with many images and compose a scene, then you export from Composer and all of the images get 'stitched' into a single composition that you can interact with at run-time.  You interact (like zooming, opacity, etc.) with your image by changing the properties of your MultiScaleImage.
    • A "Collection of Images" is when you compose a scene but export it as a collection of images (duh jaime do u get paid by the word? word, word?)
      At run-time, you can still interact with each of the individual images in your composition.  
      The images are exposed via the SubImages collection of your MultiScaleImage.   You can still set properties in the MultiScaleImage and these properties will affect all the images in the collection [for example if I Zoom-in to 200% in MSI, that would impact which SubImages are visible] but with collections I also get the benefit of interacting with the SubImages directly.

    Collections have a lot more flexibility of course, but I also caution of two tiny concerns:

    • when dealing with collections,  you likely end up downloading a few more tiles as you go.  Not a huge deal
    • Your images load independently;  this again is not a huge deal unless you have huge discrepancies in size; in that case you will see your small images load earlier than your bigger ones.  [To solve this you could play with Opacity]

    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:

    • AspectRatio - readonly property ==  width/height.
    • Opacity = self-explains;  0== transparent.  1.0 == Opaque
    • ViewportOrigin == top-left corner of the image.  Stay tuned for a lot more below. This is a really interesting property and 1/3  the point of this post
    • ViewportWidth == width of the area to be displayed.  This value is in logical coordinates. For example:
      • a value of 1 displays the entire image (no zoom),
      • a value of 0.5 is 200% zoomed in and a value of 0 is completely zoomed (user cannot see the image at all).
      • A value above 1 is zooming out from the image. For example, a value of 2 means that the image will take up half the size of the MultiScaleImage  control area (50% zoom).
    • ZIndex -- self explains;  higher numbers are in front of lower ones.

    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:

    1. Make call to read XML file.  I did it from ImageOpenSucceded to not compete with download traffic for image and to know for sure that when WebClient callback happened I could access the images in the collection.

      void
      msi_ImageOpenSucceeded(objectsender, RoutedEventArgs e)
      {
         
              WebClient wc = newWebClient();
              wc.DownloadStringCompleted += newDownloadStringCompletedEventHandler(wc_DownloadStringCompleted);
              wc.DownloadStringAsync(newUri("GeneratedImages/Metadata.xml", UriKind.Relative));     
      }
    2. Then we read the results.    The code is below.  I used LINQ to XML -that makes it trivial :)
      The only thing worth highlighting from the code is the "map" between tags and MultiScaleSubImages. 
      metadata.xml has a ZOrder, which is a 1-based index of the position of the image in the collection.   Despite its name (ZOrder), this has nothing to do with MultiScaleImage.ZIndex .

      The actual collection is 0 based, so we  subtract one to the value read from metadata.xml    I have put red, highlighted comments on top the two most relevant lines .

    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:

    • They are normalized relative to the viewportWidth of the subimage you are dealing with.
    • Moving towards the right in the horizontal ( X) direction is actually a negative offset, so is moving towards the bottom.

    Got it?? OK! you are done.  
    If you are like me  you might want to see a sample.  Here are some below: 

    image This is a DeepZoom composition w/ two images. 
    Blue is 1200x800  ( Aspect ratio = 1.5 )
    Yellow is 600x400 ( AR = 1.5 )

    At this point ViewportOrigin = 0,0 for both...   Looks good to me.

    It is worth mentioning [if you try to make sense as you go ] that the
    ViewportWidth for blue == 1.0  [takes the whole width available]
    ViewportWidth for yellow == 2.0  [takes 1/2 the width available to the control]

    The numbers on the images are "approximations".. if you read a coord of say 900,600 that means it is around there, but not quite exact

    Let's now start changing ViewportOrigin.
    image Here I simply changed viewportOrigin in yellow image.

    My first instinct looking at this one would be  1,0 ... [since it is 600 pixels left of 0,0]
    I was wrong!!
    This is actually ViewportOrigin =  -1, 0..
    Remember, when you move an image to right or bottom, the offsets are negative.

    You want to know what would happen if VO was 1,0??
    The demo is at  http://www.cookingwithxaml.com/recipes/DeepZoomCollection/default.html

    You can play with ZIndex, Opacity and ViewportOrigin for each image [their values are databound on the grid].
    image Having explained that the ViewportOrigins offsets (to right,bottom) are negative numbers.
    Can you guess what the offset is for the image to the right?
    My guess was ( 0, -1) but then  I was wrong again!  
    The ViewportOrigin here is ( 0, -.66666666666)
    Why?

    Because the offsets are relative to the Width and in this case it is 600.
    So a viewport of (0,-1) would have been lower in the y axis [instead of at 400, it would be at 600]
    image This is 0,-1 and exactly what we expected for this one (after reading line above).
    image I know you have it by now, but just for fun, this is  ( -1.5, -.3333)  aka ( 900,200)
    Notice how half of our yellow image is clipped. 
    image This is ViewportOrigin ( 0.5, -.3333 ) ... I figured I should show you some thing with a positive value for Viewport Origin...


     
    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..

  • Jaime Rodriguez

    Styling Microsoft’s WPF datagrid

    • 9 Comments

    Microsoft’s WPF datagrid has a lot of properties and styles you can tweak to get it looking right (if you are a designer). 
    Below, find my cheat sheet to styling the grid.   It is not 100% comprehensive but it gets you far and has a few very useful tips & gotchas.

    At the highest level in the DataGrid , you can change the look & feel by setting some of these:

    Property Type Values Default
    AlternatingRowBackground Brush Any Brush Null
    Background Brush Any Brush Theme default
    ColumnHeaderHeight Double 0 to any positive double NaN
    ColumnHeaderStyle Style Any Style Null
    ColumnWidth DataGridLength 0 to any positive double, Auto, *, SizeToCells, SizeToHeader SizeToHeader
    HeadersVisibility DataGridHeadersVisibility All, Row, Column, None All
    MaxColumnWidth Double 0 to any positive double Positive Infinity
    MaxRowHeight Double 0 to any positive double Positive Infinity
    MinColumnWidth Double 0 to any positive double 20
    MinRowHeight Double 0 to any positive double 0
    RowBackground Brush Any Brush Theme default
    RowDetailsVisibilityMode DataGridRowDetailsVisibilityMode Visible, VisibleWhenSelected, Collapsed VisibleWhenSelected
    RowHeadersWidth Double 0 to any positive double NaN
    RowHeight Double 0 to any positive double NaN
    AlternationCount int 2+ coerced to 2
    GridLinesVisibility DataGridGridLinesVisibility All, Horizontal, Vertical, None All
    HorizontalGridLinesBrush Brush Any Brush Black(via metadata)
    VerticalGridLinesBrush Brush Any Brush Black(via metadata)
    ItemTemplate DataTemplate Any DataTemplate Null
    RowDetailsTemplate DataTemplate Any DataTemplate Null
    CellStyle Style Any Style Null
    ItemContainerStyle Style Any Style Null
    RowHeaderStyle Style Any Style Null
    RowStyle Style Any Style Null
    Style Style Any Style Null
    Template ControlTemplate ControlTemplate TargetType=Datagrid Null

     

     

    Here, you can see a visual representation for a few of these properties (the visual is not all inclusive); this will give you an idea of what this article will cover.

    DataGridVisually2

     

    Backgrounds:
    The interesting part are the relationships amongst the backgrounds:

    • Background – sets the whole data grid’s background.  Notice it can be any brush, solid and gradients is obvious, but why not a DrawingBrush like the bear above ( which you can see if you squint hard, it has Opacity =0.1)
    • RowBackground  and AlternatingRowBackground  set the background for a row and alternating row. 
      Both of these have a higher z-order than DataGrid’s background of course, which means you can get visual composition w/ the grid’s background. 
      Notice that the default color for RowBackground is theme based (and default value is opaque); your DataGrid’s background will not be visible unless you override these row backgrounds to be partially transparent.
    • AlternationCount is the total number of styles or colors that will be used for the rows.  This number is one-index based (meaning count starts at 1, not 0).
      • If you set AlternationCount > 2, your rows from 3rd row to AlternationCount will be assigned the default background brush value (from the theme).
      • The way to set the different backgrounds or styles for each row based on AlternationCount is by overriding the style for your DataGridRow and triggering based on AlternationIndex, which is actually zero-index based.  
      • If you set the AlternatingRowBackground brush, it will be assigned to the rows where the ( rownumber%AlternationIdex ) == 1

    Here is an example of overriding RowStyle to tweak background based on AlternationIndex:

     <Style x:Key="DataGridDemoRowStyle"  
           TargetType="{x:Type Custom:DataGridRow}">
        <Style.Triggers>
            <Trigger Property="AlternationIndex" Value="2" >
                <Setter Property="Background" Value="{StaticResource RowBackgroundAlternationIndex2Brush}" />
            </Trigger>
            <Trigger Property="AlternationIndex" Value="3">
                <Setter Property="Background" Value="{StaticResource RowBackgroundAlternationIndex3Brush}" />
            </Trigger>
        </Style.Triggers>
    </Style> 

    Notice that, on purpose, I only override AlternationIndex = 2,3.  For AlternationIndex=0, it uses RowBackground.
    For AlternationIndex = 1, it uses AlternatingRowBackground from the datagrid. 
     

     

    Datagrid Column Headers

    I usually customize the header on a data grid to accomplish one of two tasks:

    • Tweak the background of the headers, including triggers for hovers, selected, etc.
    • Tweak the Control template of the header, mostly because the default style to show Sorting is on top of ColumnHeader and I like it on the side.

    My instinct was that customizing the header’s background would be a simple style override. Here is my try:

    <Style x:Key="DataGridColumnHeaderStyle" TargetType="{x:Type Custom:DataGridColumnHeader}"  >
        <Setter Property="Background" Value="#88800080" />
            <Setter Property="Foreground" Value="White" /> 
            <Style.Triggers>
            <Trigger Property="SortDirection" Value="{x:Null}">
                <Setter Property="Background" Value="{DynamicResource DataGridHeaderBackgroundBrush}" />
                <Setter Property="BorderBrush"  Value="Transparent" />
            </Trigger>
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsMouseOver" Value="True" />
                    <Condition Property="SortDirection" Value="{x:Null}" />
                </MultiTrigger.Conditions>
                <Setter Property="Background" Value="{StaticResource DataGridHeaderMouseOverBackgroundBrush}" />
                <Setter Property="BorderBrush" Value="{StaticResource DataGridHeaderBorderBrush}" />
            </MultiTrigger>
    
            <MultiTrigger>
                <MultiTrigger.Conditions>
                    <Condition Property="IsMouseOver" Value="true" />
                    <Condition Property="SortDirection" Value="{x:Null}" />
                </MultiTrigger.Conditions>
                <Setter Property="Background" Value="{StaticResource DataGridHeaderMouseOverBackgroundBrush}" />
                <Setter Property="BorderBrush" Value="{StaticResource DataGridHeaderBorderBrush}" />
            </MultiTrigger>
            <Trigger Property="SortDirection" Value="Ascending">
                <Setter Property="Background" Value="{StaticResource DataGridHeaderSortedBackgroundBrush}" />
            </Trigger>
            <Trigger Property="SortDirection" Value="Descending">
                <Setter Property="Background" Value="{StaticResource DataGridHeaderSortedBackgroundBrush}" />
            </Trigger>
        </Style.Triggers>
    </Style>

    If you run the sample code against that style, you will notice that the Sort direction arrow that is shown in the default style for the Datagrid disappeared'; the reason for it is that DataGridColumnHeader uses DataGridHeaderBorder in its template;  DataGridHeaderBorder is a kind of smart Border that checks if you set a Background and if you did, it behaves like a Border; if you did not set a Background, it then acts smartly and does the code to render the triangle indicator for sort. 

    If you do want sort direction arrows, and a different background you should just override the template and use a regular Border or what ever you want for the background.  Overriding the template is not too hard, here is an example:

    <Style x:Key="DatagridColumnHeaderCustomTemplateStyle" 
             TargetType="{x:Type Custom:DataGridColumnHeader}">
          <Setter Property="SnapsToDevicePixels" Value="True" />
          <Setter Property="MinWidth" Value="0" />
          <Setter Property="MinHeight" Value="28" />
          <Setter Property="Foreground" Value="White" />
          <Setter Property="Cursor" Value="Hand" />
          <Setter Property="Template">
              <Setter.Value>
                  <ControlTemplate TargetType="{x:Type Custom:DataGridColumnHeader}">
                      <Grid>
                          <Grid.ColumnDefinitions>
                              <ColumnDefinition Width="*" />
                              <ColumnDefinition Width="Auto" />
                          </Grid.ColumnDefinitions>
                          <Border x:Name="BackgroundBorder" BorderThickness="0,1,0,1" 
                                  Background="{StaticResource DataGridHeaderSortedBackgroundBrush}" 
                                  BorderBrush="{StaticResource DataGridHeaderSortedBorderBrush}" 
                                  Grid.ColumnSpan="2" />
                          <ContentPresenter Margin="6,3,6,3" VerticalAlignment="Center" />
                          <Path x:Name="SortArrow" Visibility="Collapsed" Data="M0,0 L1,0 0.5,1 z" Stretch="Fill" 
                                Grid.Column="1" Width="8" Height="6" Fill="White" Margin="0,0,8,0" 
                                VerticalAlignment="Center" RenderTransformOrigin="0.5,0.4" />
                          <Rectangle Width="1" Fill="#AAC377" HorizontalAlignment="Right" Grid.ColumnSpan="2" />
    
                          <Rectangle Width="1" Margin="0,0,1,0" Fill="#425B10" 
                                     HorizontalAlignment="Right" Grid.ColumnSpan="2" />
                          <Thumb x:Name="PART_LeftHeaderGripper" HorizontalAlignment="Left" 
                                 Style="{StaticResource ColumnHeaderGripperStyle}"/>
                          <Thumb x:Name="PART_RightHeaderGripper" HorizontalAlignment="Right" 
                                 Style="{StaticResource ColumnHeaderGripperStyle}"/>
                      </Grid>
                      <ControlTemplate.Triggers>
                          <Trigger Property="SortDirection" Value="{x:Null}">
                              <Setter TargetName="BackgroundBorder" Property="Background" 
                                      Value="{DynamicResource DataGridHeaderBackgroundBrush}" />
                              <Setter TargetName="BackgroundBorder" Property="BorderBrush"  
                                      Value="Transparent" />
                          </Trigger>
                          <MultiTrigger>
                              <MultiTrigger.Conditions>
                                  <Condition Property="IsMouseOver" Value="True" />
                                  <Condition Property="SortDirection" Value="{x:Null}" />
                              </MultiTrigger.Conditions>
                              <Setter Property="Background" TargetName="BackgroundBorder" 
                                      Value="{StaticResource DataGridHeaderMouseOverBackgroundBrush}" />
                              <Setter Property="BorderBrush" TargetName="BackgroundBorder" 
                                      Value="{StaticResource DataGridHeaderBorderBrush}" />
                          </MultiTrigger>
                          <MultiTrigger>
                              <MultiTrigger.Conditions>
                                  <Condition Property="IsMouseOver" Value="true" />
                                  <Condition Property="SortDirection" Value="{x:Null}" />
                              </MultiTrigger.Conditions>
                              <Setter TargetName="BackgroundBorder" Property="Background" 
                                      Value="{StaticResource DataGridHeaderMouseOverBackgroundBrush}" />
                              <Setter TargetName="BackgroundBorder" Property="BorderBrush" 
                                      Value="{StaticResource DataGridHeaderBorderBrush}" />
                          </MultiTrigger>
    
                          <Trigger Property="SortDirection" Value="Ascending">
                              <Setter TargetName="SortArrow" Property="Visibility" Value="Visible" />
                              <Setter TargetName="SortArrow" Property="RenderTransform">
                                  <Setter.Value>
                                      <RotateTransform Angle="180" />
                                  </Setter.Value>
                              </Setter>
                          </Trigger>
                          <Trigger Property="SortDirection" Value="Descending">
                              <Setter TargetName="SortArrow" Property="Visibility" Value="Visible" />
                          </Trigger>
                          <Trigger Property="DisplayIndex" Value="0">
                              <Setter Property="Visibility" Value="Collapsed" 
                                      TargetName="PART_LeftHeaderGripper"></Setter>
                          </Trigger>
                      </ControlTemplate.Triggers>
                  </ControlTemplate>
              </Setter.Value>
          </Setter>
      </Style>

    A few things to notice above:  I replaced DataGridHeaderBorder for a normal border; I added a little “triangle” for sort direction, and then transform it (or flip it) based on SortDirection.

     

    DataGrid Row Headers

    For me, these are the most common ‘tweaks’ to RowHeader.

    • Tweaking the width (as the default is too small)
    • Tweaking the background to match my theme.
    • Implementing row selection by clicking on the row header; this feature does not come out of the box.
    • Error handling happens in the RowHeader

    My very first try when looking at the API was to set Row Header Width via styles.  Later on, I realized that DataGrid exposed the RowHeaderWidth property directly so I am now using that instead.  This is a trivial property setter.

    For tweaking  the background,  I first tried setting a the RowHeader style property in the datagrid. The basic style I tried looked like this: 

     <Style x:Key="DataGridRowHeaderBackgroundStyle" TargetType="{x:Type Custom:DataGridRowHeader}">
            <Setter Property="Background" Value="Gray" />
        </Style>
    

     

    It works, but similar to ColumnHeaders I lost functionality.  At run-time, it looked like this:

    RowHeaderBackgroundOnly

    As you will notice, it lost the row DataGridLines that separates each row; there are no hovers, etc. 
    I then proceeded to override the template.  The change was actually trivial, I noticed that DataGridHeaderBorder defaults back to the rendering for it’s base class (Border),  so this mostly implied setting a BorderThickness on it to fake the grid’s row separators, and binding the color to the DataGrid’s HorizontalGridLinesBrush.. 

    Here is the template that I created for the DataGridRowHeader.. (and below the explanation on a few extra gotchas).

    <Stylex:Key="{x:TypeCustom:DataGridRowHeader}"TargetType="{x:TypeCustom:DataGridRowHeader}">
        <
    SetterProperty="Background"Value="{StaticResource RowHeaderBackgroundBrush}" />     
        <
    SetterProperty="Template">
            <
    Setter.Value>
                <
    ControlTemplate TargetType="{x:TypeCustom:DataGridRowHeader}">
                    <
    Grid>                       
                        <
    Custom:DataGridHeaderBorder IsSelected="{TemplateBinding IsRowSelected}"
                                    
    IsHovered ="{TemplateBinding IsMouseOver}"
                                    
    IsPressed="{TemplateBinding IsPressed}"
                                    
    BorderBrush="{Binding RelativeSource={RelativeSource AncestorType={x:Type Custom:DataGrid}},
                                       
    Path=HorizontalGridLinesBrush}"
                                    
    Background="{TemplateBinding Background}"                                    
                                    
    BorderThickness="0,1,0,0"
                                    
    Padding ="{TemplateBinding Padding}"
                                    
    Orientation="Horizontal"
                                    
    SeparatorVisibility="{TemplateBinding SeparatorVisibility}"
                                    
    SeparatorBrush="{TemplateBinding SeparatorBrush}" Margin="0,-1,0,0">

                            <
    StackPanel Orientation="Horizontal">
                                <
    ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                                                 
    VerticalAlignment="Center"/>
                                <
    Control SnapsToDevicePixels="false"
                          
    Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Custom:DataGridRow}},
                                   
    Path=(Validation.HasError),
                          
    Converter={StaticResource bool2VisibilityConverter}}"
                          
    Template="{Binding RelativeSource={RelativeSource AncestorType={x:Type Custom:DataGridRow}},
                                   
    Path=ValidationErrorTemplate}" />
                            </
    StackPanel>
                        </
    Custom:DataGridHeaderBorder>
                        <
    Thumb x:Name="PART_TopHeaderGripper"
                  
    VerticalAlignment="Top" Height="3"
                  
    Style="{StaticResource RowHeaderGripperStyle}"/>
                        <
    Thumb x:Name="PART_BottomHeaderGripper"
                  
    VerticalAlignment="Bottom" Height="3"
                  
    Style="{StaticResource RowHeaderGripperStyle}"/>
                    </
    Grid>

                    <
    ControlTemplate.Triggers>                       
                        <
    Trigger Property="IsMouseOver" Value="True">
                            <
    Setter Property="Background" Value="{StaticResource RowHeaderIsMouseOverBrush}" />
                        </
    Trigger>
                        <
    Trigger Property="IsRowSelected" Value="True">
                            <
    Setter Property="Background" Value="{StaticResource RowBackgroundSelectedBrush}" />
                        </
    Trigger>
                    </
    ControlTemplate.Triggers>
                </
    ControlTemplate>
            </
    Setter.Value>
        </
    Setter>
    </
    Style>

     

    The interesting changes were:

    • I had to use an implicit style.  Though the DataGrid does have have RowHeaderStyle property, it some how did not work for me; which is weird because RowHeaderStyle worked fine when I used style that did not override the template.
    • The BorderThickness of DataGridHeaderBorder is set to 0,1,0,0..  and that makes it draw the equivalent of a GridLine,  I offseted the Margin by 0,-1,0,0  to make sure this aligned with the DataGridRow GridLines.
    • BorderBrush in DataGridHeaderBorder is bound to the DataGrid’s HorizontalGridLinesBrush.
    • I went ahead and added a trigger for IsRowSelected, bound to a local Brush in the dictionary. So now the RowHeader will display its Selected state visually.
    • I added a trigger for IsMouseOver,   it is just ‘expected behavior’.
    • I set a Height of Size 3 to the Thumbs used for the grippers that resize the row. The reason I did this is because I like to be able to double click on a header and have it select the whole Row; this functionality is implemented in the datagrid,  but the Thumbs are so big that they get on the way of trying to click in the RowHeader.  A size of 2 or 3 for the Thumbs seems to do fine for dragging and leaves enough room for clicking on the RowHeader to select row.
    • Another interesting feature I learned when playing with RowHeader was that if you double  click in the Thumbs that resize the row, it goes back to its original size.  Nice touch (that I did not know about).

    Moving on to the task of reporting errors in the RowHeader,  I did not tweak the DataGridRowHeader at all to do any thing related to errors.  I did it all via the DataGrid’s ErrorTemplate property to point to ErrorTemplate2 in my resource dictionary.

    <ControlTemplate x:Key="ErrorTemplate2">
           <Grid  MinWidth="20" MinHeight="20">
                <Rectangle Fill="{StaticResource ErrorTemplateBrush}" />      
           </Grid> 
    </ControlTemplate>
    

     

    <digression>
    I do not like that ErrorTemplate is a ControlTemplate. In my opinion it should be a DataTemplate with access to the DatagridRow’s context and the DatagridRow’s error collection.   As a ‘workaround you can try to pass this into the control yourself by tweaking the  RowHeaderTemplate, and passing the DataContext into the control that acts as placeholder for ErrorTemplate, like this:

    <Control SnapsToDevicePixels="false"
    Visibility="{Binding RelativeSource={RelativeSource AncestorType={x:Type Custom:DataGridRow}}, 
            Path=(Validation.HasError), 
    Converter={StaticResource bool2VisibilityConverter}}"
    Template="{Binding RelativeSource={RelativeSource AncestorType={x:Type Custom:DataGridRow}}, 
            Path=ValidationErrorTemplate}" 
                     DataContext="{Binding
                        RelativeSource={RelativeSource  AncestorType={x:Type Custom:DataGridRow}},
                        Path=(Validation.Errors)[0].ErrorContent }"                                             
                        >

    You can then tweak the ErrorTemplate datagrid with a tooltip:

    <ControlTemplate x:Key="ErrorTemplate2">
          <Grid  MinWidth="20" MinHeight="20" ToolTip="{Binding}">
               <Rectangle Fill="{StaticResource ErrorTemplateBrush}" >               
               </Rectangle>             
           </Grid> 
    </ControlTemplate>

    and get something more helpful error message, like this:

    ToolTipError

    </digression>

     

    Cell Styles

    By default the DataGrid’s cell show a themed, blue background when selected (see image in closing thoughts below), I did not like that, so I used DataGrid’s CellStyle to take care of that. Override the default template and remove the triggers for selection:

    <Style x:Key="DataGridCellStyle" TargetType="{x:Type Custom:DataGridCell}">
            <Setter Property="Background" Value="Transparent" />
            <Setter Property="BorderBrush" Value="Transparent" />
            <Setter Property="BorderThickness" Value="1" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Custom:DataGridCell}">
                        <Border Background="Transparent" 
                      BorderBrush="{TemplateBinding BorderBrush}"  
                      BorderThickness="0" 
                      SnapsToDevicePixels="True">
                            <ContentPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

     

    RowDetailsTemplate

    The RowDetails Template is displayed when a row is selected. It is a DataTemplate with the context of the row.  In this demo, the implementation is trivial, all I did was put a textblock, but you can do much more complex RowDetails on a real project.

    <DataTemplate x:Key="RowDetailsTemplate"> 
            <Grid TextBlock.Foreground="White"> 
                <Grid.RowDefinitions>
                    <RowDefinition /> 
                    <RowDefinition />                 
                </Grid.RowDefinitions>
                <TextBlock Text="RowDetails Go here"  Grid.Row="0"/>             
                <TextBlock Text="{Binding }" Grid.Row="1"/> 
            </Grid>
            
    </DataTemplate>

    The main reason to mention RowDetailsTemplate is to emphasize the ‘synchronization’ that needs to happen when a row is selected:  RowDetailsTemplate, RowBackground, and RowHeader’s background should  all align to make sure their backgrounds are color coordinated.   In this case, if you look at the templates above, I did make sure they matched for selection and set the background to the ‘dark blue’ gradient.


    Closing Thoughts:
    This writing is not all inclusive; there is lots more you can do to style the datagrid. 
    I do have to say, it is neat that in the usual “WPF designer friendly” mark-up tweaks, we went from a plain grid (see left) to a styled grid ( see right) with out writing a single line of code.  

    CheesyDataGridSummary

     

    What now?
    To compliment this styling tutorial, I recommend Colin Eberhardt’sWPF Datagrid Practical Examples” article. He does a great job at sharing insights into data binding, validating and even styling the datagrid. Thanks Colin!

     
    The source code for this writing is here.   Thanks for reading this

  • Jaime Rodriguez

    Early version of snoop for silverlight 1.0 ...

    • 9 Comments

    First 3 hours of troubleshooting the problem my last post, I was thinking "I need snoop" ... 

    Next 5 hours, I wrote a very early rough version ...  It needs work,  I want to clean it up, but have not have a chance..  Sharing it in the mean time  and adding this to my very long "to clean up later" list ...   [email me if you want to be notified or think it would be useful]..

    Here is what it does:
    1 - Creates a VisualTree ...    [in a different Silverlight control, as not to bother your code]
    2 - you can click on any item in the visual tree and it will adorn it in the control ..   [see known issues]
    3 -- It can subscribe to either MouseMove or MouseEnter/MouseLeave in your code ... so you can get details on the items that the mouse hovers over..  of course for this, your item needs to be hitTestVisible...
    I did both events cause i thought mouseMove would be too much.. but I ended up adding a cache and MouseMove seems OK.. Still MouseEnter is likely a better choice for very large project.. the one I tested had about 2000 visuals..
    4-  I added a flag to skip paths...  this turned useful for me, but if you want paths, then change the flag in VisualTreeHelper.js

    Here is a sample screenshot of the tree + details..  the LHS is the VisualTree,  the RHS is a Silverlight control with items..  the Adorned item is in yellow..

     

    Here are the known issues:

    1- My treeview control and its scrollbar sucks .. sorry .. would have taken me longer to write the tree than the snoop functionality..   
    2 --  If you are clicking on the tree, it does its best to guess the position for the adorner, but currently it ignores transforms... so it is more accurate if you subscribe to mouse move... or Mouse Enter .. cause then it caches the position it should adorn so it is accurate..
    3 -- tree 'ignores' changes once tree is built ... but you do have the ability to "refresh the tree" ...
    4 -- in my machine I wired it in same window..   but for sample, to try to make it usable for people with lower resolutions, I hacked some very ugly  buttons for a new window..  the problem is if you close the window with out unplugging the event listeners you will get errors.. 

    My current wishlist:

    1. I should make it so it can edit the values in the details tab... that should be easy and quite handy ...
    2. Gotta fix known issues.. specially UI..  
    3. Still debating the re-write in 1.1 .. I would benefit from perf, but I thought keeping it 1.0 to make sure any one is comfortable..  [instead of people thinking, well I am in 1.0 would this work if it is 1.1?]

    Want to take it for a spin ???   It is "live" here ...   The source is here..  

    The sample is no where representative of what this early snoop helped me troubleshoot... To see some of the intricacies, follow this sequence..

    1. Click on the "Create window" button ..  wait until it loads ( gray background appears )
    2. Click on the "Create visual tree" to get a tree...
      1. Click around in the tree.. to see it adorn some of the elements in the page..
      2. Good example, click on the "invisibleEllipse" element ..
    3. Subscribe to Mouse Move events..  (by clicking "3- subscribe to move"  in the controls page)
      1. Move the mouse around... to see the adorner and how the tree keeps track of element with mouse focus ..
      2. if you navigate around the bottom canvases, notice how some items are not hittestable cause there is an invisible rect in front.. very last item in the tree, called the "hog" ..
    4. Go back to visual tree and high light these not reachable items..   see how the adorner finds them


    if you take this early sample and make it useful or good quality, please drop me an email so I can redirect ...  If you run into issues, let me know too ..  

    PS -- apologies to Peter for calling it 'snoop' when it does 0.1 of what snoop does..  Peter, imitation is flattery ..

  • Jaime Rodriguez

    cheat-sheet to some of the WPF 3.5 SP1 features..

    • 8 Comments

    .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:

    • Adam Kinney interviewed David Teitlebaum around beta1 time-frame about the graphics improvements: hardware accelerated bitmap effects, D3DImage, WriteableBitmap, etc.
    • Greg Schecter has this great series on hardware accelerated bitmap effects.
    • Dr. WPF's tutorial on D3DImage is very comprehensive.  This is a niche feature but it does enable a lot of scenarios where the previous solution [with airspace] was not ideal.   BTW, happy blog-anniversary Dr. WPF!

    AppModel:

    • Adam Kinney has a great interview with Jennifer Lee.   I must say I am a bit surprised there is not more out there XBAPs.  XBAP got a bad reputation at 3.0 launch because it did not work on Firefox, and at the time WCF required Full trust and XBAPs did not elevate. All of that has been fixed now, so I am excited about the improvements they did for XBAP in 3.5 SP1 (like the HTML splashscreen).
    • Lester's has a sample too on WebBrowser control [this is a very handy feature, compared to frame].

    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]...

  • Jaime Rodriguez

    On WPF reference applications and the new location for the WPF Hands-on-lab for building the Outlook UI?

    • 8 Comments

    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..

  • Jaime Rodriguez

    datagrid (part 2) -- Show me some code.

    • 7 Comments

    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:
    datagridFinal 

    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" />  
    From above, notice I mostly passed strings into the header property.  This was my choice; I could have more complex objects since Headers are Content controls and have a HeaderTemplate, but I did not need it here.  


    The symbol column  is a DataGridHyperlinkColumn; I did nothing to customize it. If you compare it to DataGridTextColumn you will see an extra property. On the DataGridHyperlinkColumn,  DataFieldBinding -- looks or expects a Uri.  and the ContentBinding looks for the text that the UI will display.

     

    <dg:DataGridHyperlinkColumn DataFieldBinding="{Binding SymbolUri}"  
    ContentBinding="{Binding Symbol}" Header="Symbol" SortMemberPath="Symbol"/>



    DataFieldBinding  - is where the data (Uri) is coming from.  
    ContentBinding - is the 'text' that is displayed on the hyperlink. 
    SortMemberPath - is the data used for sorting. The datagrid will look at the property this path points to and if it implements IComparer will automatically handle the sorting. [In this app, most of the columns sort and I implemented no sorting logic at all :)]  

    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:

    datagridNan

    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.

  • Jaime Rodriguez

    Thank You LA!! Here is the content

    • 7 Comments

    Last week we had our first stop in the WPF for LOB tour: Los Angeles.    hOLLYWOODxamlIZEDThe training went very well!  Attendees were very engaged and they kept up with a lot of content in a short time-span; from what I heard, they learned quite a bit.

    Karl and I learned a bit too! We are already refining our content for the next stop:
    London (on 5/15 and 5/16) at the
                  Radisson Edwardian Heathrow Hotel
                  140 Bath Road
                  Hayes Middlesex , London

    Thanks LA! The content for the training is here: decks (30 mb), demos (15mb) 


    If you have not signed up for your training ( in London,  NY, Chicago, or Phoenix), I recommend you get to it soon; London and NY are already full, but we are adding names to the waiting list.   Chicago and Phoenix are getting there!.   Huge thanks to all those helping us spread the word!

    London residents, see you soon!    LA attendees, thanks again!! It was a lot of fun!!

  • Jaime Rodriguez

    Announcing the Building Windows 8 apps with C++ Windows camp

    • 7 Comments

    Join the Microsoft Visual C++ and Windows teams in Redmond on May 18, 2012 for a free, all-day event focused on building Windows 8 Metro style apps with C++.

     

    Whether you are a new C++ developer ready to learn about the writing Metro style apps, an intermediate developer who wants to hone your code and skills, or an experienced C++ developer eager to squeeze every ounce of performance out of your Metro style app and/or push the boundaries of Windows 8, then this event is for you. We will have pragmatic advice for every developer writing Metro style apps and games with XAML or DirectX and C++.

    Agenda:

    • C++ for Windows 8, Keynote by Herb Sutter
    • Building Windows 8 apps with XAML and C++
    • Building Windows 8 games with DirectX and C++
    • Introduction to the Windows Runtime Library (WRL)
    • Writing Connected apps: Writing networking code with C++
    • Combining XAML & Direct X in a Metro style apps
    • Writing WinRT components to be consumed from any language
    • VC11 compiler flags for getting the most out of C++

    Registration:
    Register for this event hereIf is first-come, first-serve, and we are limited in space, so register soon.
    If you want to learn more about other Windows 8 camps check the camps site.  

    Details:
    Location:
    Redmond WA, Microsoft campus, bldg 92.
    Date:  May 18th, 2012
    Time:  9 AM to 5 PM for sessions,  Q&A and a small social event afterwards.
    Speakers:  We will update the list of speakers early next week, we are still negotiating session times & speakers to cram as much as we can into a single day. 
    Rest assured most of them are from the product team and yes, Herb Sutter will do the opening keynote and the first session.
    Meals:  We will have some light breakfast, lunch, snacks and appetizers + drinks at the end of the day.

    My personal pitch on this event:
    Even if you are already coding your Metro style app, you don’t want to miss this event. All the speakers are product team people, and we will have a very strong pragmatic angle during our sessions; we are aiming to answer a lot of the questions and help you avoid the common pit-falls that we have seen our early partners building Metro style apps with C++ have ran into.  There will also be ample Q&A time through out the day.

    Happy Windows 8 coding.  Please help us spread the word on this event we are really excited to connect with C++ community.

  • Jaime Rodriguez

    Introduction to WPF 4 Multitouch

    • 6 Comments

    This tutorial recaps the multitouch features in WPF 4, as of the Beta 2 release.
    I also included two basic samples to get you jumpstarted with working code:

    A Multitouch Backgrounder

    Multitouch is simply an abstraction from the OS (or a platform) that routes touch input to an application. 
    The OS exposes multitouch input with different levels of control and/or detail.  For example, Windows 7 exposes multitouch data in three modes:

    • Raw touch provides access to all touch messages. Aggregation or interpretation of the messages is left to the application. This level of access is useful for programs that require raw access to all the primitives possibly for some custom interpretation and handling of the messages. For example Corel Paint It Touch and Windows 7 Paint require this level of control to implement drawing. Other possible consumers for this level of detail are custom control or platform vendors.
    • Gestures is a very convenient abstraction from raw-touch.
      The platform interprets all the lower level events and translates them into pre-defined gestures, then notifies the application that a gesture has occurred.  The most common gestures are pan, zoom, rotate, and tap.
      Gestures offers a very easy programming model, but it has a limitation: gestures engines tend to handle only one gesture at a time ( for example, rotate or zoom, but not a rotate with zoom).  
    • Manipulation and inertia.   
      Manipulation is a superset of gestures; any thing you can do with gestures, you can do with manipulations, but you gain greater granularity and flexibility.  Of course, the trade-off is that manipulation is a pinch harder to program than gestures, but don’t worry, both are straight forward.
      Inertia is an extension to manipulation that adds physics support to make all of your manipulations smooth and realistic.

    If you are not familiar with multitouch, I recommend these articles on multitouch in Windows 7:

    Now that you are a touch expert,  we can simply focus on explaining WPF’s support.

    Multitouch in WPF 4

    WPF 4 includes support for raw touch and manipulation (with some inertia support).  This support extends throughout the platform; UIElement, UIElement3D, and ContentElement have all been tweaked to support raw-touch and manipulation.

    Post beta2, WPF 4 will also support touch in some of the controls (for example, ScrollViewer). The list of controls and level of support is not yet finalized, so don’t hold it against me; I will update this post as soon as I get details.

     

    Approach #1: Raw-touch in WPF 4

    Again, raw multitouch support begins at UIElement, UIElement3D and ContentElement.
    All of these types now support a TouchDown, TouchUp, TouchMove, TouchEnter and TouchLeave event.
    Each of these events have a corresponding routed event and a tunneling (Preview) event. 

    • public static readonly RoutedEvent TouchDownEvent;
    • public static readonly RoutedEvent TouchEnterEvent;
    • public static readonly RoutedEvent TouchLeaveEvent;
    • public static readonly RoutedEvent TouchMoveEvent;
    • public static readonly RoutedEvent TouchUpEvent;

     

    If you drill down through these events, you will find they all have a TouchEventArgs parameter that holds a TouchDevice member and can get you a TouchPoint.  The TouchPoint is the meaningful data since it tells you whether it was a Up,Down, or Move TouchAction, and it tells you the Position where the touch happened.

    I have included a class diagram below; the names are pretty descriptive.

    Touch

     

    Handling raw touch in WPF is really as simple as listening for these events and reacting to the points and the actions. 
    Unlike Manipulation where you do have to opt-in by setting the IsManipulationEnabled property to true,  event notifications for raw touch are available without an explicit opt-in


    A sample application for raw touch

    Of course, for raw touch I had to create the canonical helloMT drawing pad.   

    Disclaimer: I took the code written by Sela to demonstrate the .NET wrappers for Windows 7 multitouch and simply ported it to WPF 4.  Taking their apps and porting them to WPF 4 was about a 15 minute exercise.

    Download the source code.  

    When running the app, simply apply multiple fingers through the window to have the drawing pad draw strokes that follow your fingers’ movements.


     

    Approach #2: Manipulation in WPF 4

    Manipulation in WPF 4 is an opt-in behavior.  There is a simple process to handle manipulation events in any WPF element:

    1. Set IsManipulationEnabled=true on the element you are touch enabling. You can do this from XAML or from code. 

      <
      Image x:Name="image"Width="200"IsManipulationEnabled="True" Source="Windows7.png">
    2. [Optional] Subscribe to ManipulationStarting and set your ContainerElement. 
      The ContainerElement is the UI element to which all manipulation calculations and events are relative. If you do not set a ContainerElement, the UI element that is firing the event will be used. This works well for Zoom/Scale, but for all Translate or Rotate manipulations you should set a ContainerElement, or else the UI will flicker and be jumpy. This is not a UI glitch, it happens because a single manipulation will fire multiple deltas, so you are recording movements relative to the UI element that is being moved. Not cool!

      In ManipulationStarting, you can also set your ManipulationMode to control the manipulations you are allowing. You can select from All | None | Rotate | Translate | Scale | TranslateX | TranslateY. If you don’t override it, the default is All.

      Finally, if you want to do single hand rotations, you can set a Pivot that your UI element will rotate around.
       void image_ManipulationStarting(object sender, ManipulationStartingEventArgs e)
       {   
              //canvas is the parent of the image starting the manipulation;
              //Container does not have to be parent, but that is the most common scenario
               e.ManipulationContainer = canvas; 
              // you could set the mode here too 
              // e.Mode = ManipulationModes.All;             
       }
    3. Subscribe to the ManipulationDelta event in the UI Element (or an element higher in the Visual tree, since the event is routed).  ManipulationDelta is where all the action happens.  I have included a “default” implementation below, with good commenting.
      void image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
      {
          //this just gets the source. 
          // I cast it to FE because I wanted to use ActualWidth for Center. You could try RenderSize as alternate
          var element = e.Source as FrameworkElement; 
          if ( element != null ) 
          { 
              //e.DeltaManipulation has the changes 
              // Scale is a delta multiplier; 1.0 is last size,  (so 1.1 == scale 10%, 0.8 = shrink 20%) 
              // Rotate = Rotation, in degrees
              // Pan = Translation, == Translate offset, in Device Independent Pixels 
               
              var deltaManipulation = e.DeltaManipulation; 
              var matrix  = ((MatrixTransform)element.RenderTransform).Matrix;            
              // find the old center; arguaby this could be cached 
              Point center =  new Point ( element.ActualWidth/2, element.ActualHeight/2) ;
              // transform it to take into account transforms from previous manipulations 
              center = matrix.Transform(center); 
              //this will be a Zoom. 
              matrix.ScaleAt(deltaManipulation.Scale.X, deltaManipulation.Scale.Y, center.X, center.Y); 
              // Rotation 
              matrix.RotateAt(e.DeltaManipulation.Rotation, center.X, center.Y);             
              //Translation (pan) 
              matrix.Translate(e.DeltaManipulation.Translation.X, e.DeltaManipulation.Translation.Y);
      
              ((MatrixTransform)element.RenderTransform).Matrix = matrix; 
      
              e.Handled = true;
          }
      }


    That is how simple manipulation is. All the raw-touch data, translated into these simple Delta Matrixes!  

    Enhancing Manipulation with Inertia

    Inertia adds physics to a manipulation to make it feel more natural.  As expected, it works on all UI elements that support manipulation. The way to think of inertia is that it carries through the physical momentum of a manipulation. For example, if you are implementing a translation manipulation that is moving an image across the X-axis, inertia will continue the manipulation a bit longer than the actual manipulation contact and it would decelerate at a speed you define, simulating the momentum and the friction to stop the translation.

    To add support for inertia, we simply update our old code and listen to a new event and then add code to handle inertia on our manipulation Delta.

    1. Subscribe to ManipulationInertiaStarting. 
      This event is similar to ManipulationStarting, it gets called at the beginning of each individual manipulation. In the event handler we append parameters to the Manipulation. For inertia, the interesting properties include:
      • ExpansionBehavior – decelerates at DIPs per squared millisecond . 
      • TranslationBehavior  - decelerates at DIPs per millisecond.
      • RotationBehavior - decelerates at degrees per millisecond
      • InitialVelocities is read-only; it gives you the velocities calculated from the previous stage of the manipulation. You can use these values to calculate your own behaviors. 

        image 

        Here is the code to add our desired behaviors for inertia:

        void canvas_ManipulationInertiaStarting(object sender, ManipulationInertiaStartingEventArgs e)
        {                
                // Decrease the velocity of the Rectangle's movement by 
                // 10 inches per second every second.
                // (10 inches * 96 DIPS per inch / 1000ms^2)
                e.TranslationBehavior = new InertiaTranslationBehavior()
                {
                    InitialVelocity = e.InitialVelocities.LinearVelocity,
                    DesiredDeceleration = 10.0 * 96.0 / (1000.0 * 1000.0)
                };
        
                // Decrease the velocity of the Rectangle's resizing by 
                // 0.1 inches per second every second.
                // (0.1 inches * 96 DIPS per inch / (1000ms^2)
                e.ExpansionBehavior = new InertiaExpansionBehavior()
                {
                    InitialVelocity = e.InitialVelocities.ExpansionVelocity,
                    DesiredDeceleration = 0.1 * 96 / 1000.0 * 1000.0
                };
        
                // Decrease the velocity of the Rectangle's rotation rate by 
                // 2 rotations per second every second.
                // (2 * 360 degrees / (1000ms^2)
                e.RotationBehavior = new InertiaRotationBehavior()
                {
                    InitialVelocity = e.InitialVelocities.AngularVelocity,
                    DesiredDeceleration = 720 / (1000.0 * 1000.0)
                };
                e.Handled = true;                  
        }
      • You may notice I did not override the ManipulationContainer. This is not required; it will reuse the ManipulationContainer we set during the ManipulationStarting event.

    2. [Optional] Now, we could add code at our ManipulationDelta event handler for inertia. 
      This is optional, if you run the code at this point, inertia is already working, but you will notice there is no boundaries (the images fly off the screen).  So, just as an example, I will add code to handle the boundaries and stop the inertia when we reach the boundaries.
      void image_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
      {
          // …. this is the same code as above, in our manipulation delta.. 
      ((MatrixTransform)element.RenderTransform).Matrix = matrix; e.Handled = true; // Here is the new code.
      // We are only checking boundaries during inertia in real world, we would check all the time
      if (e.IsInertial) { Rect containingRect = new Rect(((FrameworkElement)e.ManipulationContainer).RenderSize); Rect shapeBounds = element.RenderTransform.TransformBounds(new Rect(element.RenderSize)); // Check if the element is completely in the window. // If it is not and intertia is occurring, stop the manipulation. if (e.IsInertial && !containingRect.Contains(shapeBounds)) { //Report that we have gone over our boundary e.ReportBoundaryFeedback(e.DeltaManipulation); // comment out this line to see the Window 'shake' or 'bounce' // similar to Win32 Windows when they reach a boundary; this comes for free in .NET 4 e.Complete(); } } } }


      That is it. Our image viewer now has inertia support, and we have full control on the deceleration, rotation ratios, etc.
    A sample application for Manipulation and Inertia:

    Image Viewer. Manipulation
    This sample uses the code above to manipulate the images on the canvas. 

    Download the source code

    The viewer supports scaling, translating, and rotating the images, using multitouch gestures. There is also inertia support as you execute any manipulation.

     

     

     

     

    Mixing and matching approaches:

    In WPF 4 raw-touch and manipulation are not mutually exclusive –this is different from Win32. 
    You can enable both raw touch and manipulation at the same time on any UI Element.  

    The table below explains how logic is handled for scenarios with different options enabled.

    Manipulations Enabled

    TouchDown is Handled

    GotTouchCapture is Handled

    User Logic

    WPF Logic

    None

    No

    No

    None

    Promoted to Mouse

    None

    Yes

    No

    Handled as Touch by user

    None

    None

    Yes

    Yes

    Handled as Touch by user

    None

    Enabled

    No

    No

    None

    1. Handled by Manipulation logic, TouchDevice is Captured,

    2. Manipulation logic will handle GotTouchCapture event and manipulation events will be reported

    Enabled

    Yes

    No

    Handled as Touch by user. User has to explicitly capture the touch device.

    Manipulation logic will handle GotTouchCapture event and manipulation events will be reported

    Enabled

    Yes

    Yes

    1. Handled as Touch by User.

    2. User has to explicity capture the touch device.

    3. GotCapture handled by user, user has to explicitly AddManipulator to invoke manipulation

    None

     

    Summary

    This tutorial provided a basic introduction to multitouch in WPF. As you have seen, WPF supports both raw-touch and manipulation (with inertia) for all WPF UI elements. 

    Using WPF’s new touch support, you can accomplish quite a bit with just a few lines of code. The support compliments and integrates quite well with the platform.

  • Jaime Rodriguez

    Three upcoming WPF trainings

    • 6 Comments

    Last year, Karl and I had a blast on our WPF for Line of Business tour.  It was so cool to meet all these people doing or planning to do WPF.  This year, budget is tighter, so no big tour.. but still plenty of WPF and Windows 7 training coming to a city near you..  
    Here are the details:

    April 15,  London, UKClient Development with Visual Studio 2010, .NET Framework 4 & Windows 7 at Techdays  UK Mike Taulty, Ian Griffiths, Paul Foster, and others will do a “Client Day” at Microsoft Techdays, UK.
    This is an all-day Lap Around Windows 7, .NET 4, and any thing you want to ask these guys.  Mike,Ian and Paul know everything about WPF, Silverlight, .NET and predicting the stock market. Don’t miss out on their tips.  Register here.

    April 28,29  - Redmond, WA   -- WPF For Line-Of-Business, Reloaded.
    Karl Shifflett and I will do the same two-day version of the WPF for Line Of Business training we did last year.   This is a two-day deep dive into WPF:  Day one is fundamentals, Day two has an MVVM focus.   

    This is FREE, you get breakfast, lunch and afternoon snack.  Register here. 
    First-come, First serve, limited capacity. You should hurry, but please register only if you are committed to attend.

    April 30, Redmond WA – Building Windows 7 Applications with Windows Presentation Foundation and .NET 4.
    Karl, me, and a few members of the WPF team will recap the new WPF features in .NET 4, and how to use these features to create applications that take advantage of Windows 7.

    Expect to hear about client profile, multi-touch, taskbar, all the new graphics enhancements. etc.

    This is FREE, you get breakfast, lunch and afternoon snack.  Register here. 
    First-come, First serve, limited capacity. You should hurry, but please register only if you are committed to attend.

    If you can’t make these dates or these cities. Don’t worry. The Redmond trainings will be recorded and published online. 
    That said, join us if you can.  You will learn much more in person than watching the videos. We would love to have you.

    Stay tuned for detailed agenda.  Register as soon as possible if you want a seat.

    [PS—If you have a blog, or you participate in any other form of social sharing, please spread the word.
    We thank you in advance!]

    Update 3/24 --  We will only do the locations above. Again, Redmond will be recorded and posted online. 
    As much as we would love to do a tour, this year it simply was not possible; let's try online and see if we can make it work; it could be a way to reach more people (even if not 1:1)

  • Jaime Rodriguez

    Migrating apps from Windows Phone April CTP Refresh to the beta build

    • 6 Comments


    There were a good number of breaking changes and new features introduced in the “beta” release of the Windows Phone Developer Tools.
    Going forward from beta to RTM; the number of breaking changes will be minimal, so let’s just “buckle up” and migrate our code this once.
    The migration is a lot easier than it sounds. I will first walk through the obvious breaking changes that the compiler will catch, and then share tips and workarounds in some of that ‘missing’ stuff that the compiler will miss. 
    Once you have gotten through this, don’t forget to read my post on the new features in the beta release so you can start taking advantage of these.

    Breaking changes:

    1. Namespaces and assembly changes:
      The following assemblies were removed, and merged into a single “Microsoft.Phone” assembly:
      Microsoft.Phone.Controls
      Microsoft.Phone.Controls.Navigation
      Microsoft.Phone.Controls.WebBrowser
      Microsoft.Phone.Controls.WebBrowserInterop
      Microsoft.Phone.Shell
      Microsoft.Phone.Notification
      Microsoft.Phone.Execution
      Microsoft.Phone.Info
      Microsoft.Phone.Tasks
      Microsoft.Devices
      To fix: change all your project references and all your namespaces declarations (xmlns) in XAML. See MigrationTips.1 below for details on namespaces.
    2. System.Device.Location assembly was merged into System.Device assembly
      To fix: change assembly references that were pointing to System.Device.Location and point them to System.Device assembly.
    3. Microsoft.Devices assembly was removed; the classes in this assembly were moved to Microsoft.Phone assembly.  Some of the classes changed namespaces, but not all.
      To fix: change assembly references and reference Microsoft.Phone assembly.
    4. Application.Resources have been removed from App.xaml and templates have been changed
      To fix: you will need to remove these from your own App.xaml. Can’t leave them in because it would break theming; also, there are breaking changes (controls removed) that would prevent your app from running if you do not get rid of the resources. See below on MigrationTips.2 for details on what to remove and how.
    5. ToggleControlSwitch and ToggleControlButton have been removed.
      To fix: You should  use ToggleButton and copy the template from RC version of ToggleControlSwitch. You will then need to add the touch gesture. Wait for a sample for this. I will post one soon.
    6. ListView and ListView Item were removed
      To fix: Use ListBox and a Template that matches the old ListViewItem template.
    7. Effects have been removed from the platform, this will be plan of record for v1 (the feature might come back later).
      In earlier builds, we had DropShadow, and Blur bitmap effects.  Unfortunately, these have been removed.
      Right now, your XAML is not breaking, but the usage of the effects is turning into a no-op.

      To fix: You should just remove the references to effects from XAML and code. You can use graphics (e.g. gradients for dropshadows) to try to get similar look & feel. Not exactly the same, but best workaround I can think of until Microsoft brings these back in a future version of the platform.

    8. A few classes were renamed or moved namespaces: 
      Type Old New New assembly
      (if changed)
      namespace Microsoft.Devices.NetworkInformation Microsoft.Phone.Net.NetworkInformation Microsoft.Phone.dll
      namespace Microsoft.Phone.License Microsoft.Phone.Marketplace Microsoft.Phone.dll
      class Microsoft.Phone.Controls.NavigatedEventArgs System.Windows.Navigation.NavigationEventArgs System.Windows.dll
      class Microsoft.Phone.Navigation.PhoneNavigationEventArgs System.Windows.Navigation.NavigationEventArgs System.Windows.dll
      class AccelerometerSensor Accelerometer  
      class AccelerometerReadingAsyncEventArgs AccelerometerReadingEventArgs  
      class AccelerometerStartFailedException AccelerometerFailedException  
      class WindowsPhoneEvents Microsoft.Phone.Shell.PhoneApplicationService Microsoft.Phone.dll
      class Microsoft.Phone.License.LicenseInfo Microsoft.Phone.Marketplace.LicenseInformation Microsoft.Phone.dll
      enum Microsoft.Phone.Shell.DownloadInterval Microsoft.Phone.Shell.UpdateInterval Microsoft.Phone.dll
      enum Microsoft.Phone.Shell.DownloadRecurrence Microsoft.Phone.Shell.UpdateRecurrence Microsoft.Phone.dll
      class NotificationChannelExceptionEventArgs NotificationChannelErrorEventArgs Microsoft.Phone.dll

      To fix: simply replace the namespace and/or class name and reference the new assembly (if applicable)

    9. Minor changes were made to the WMAppManifest.xml file
      1. XNA projects used to have an PlaceHolderString="Default task" on the WMAppManifiest.xml  this XML attribute is not longer valid.
         Fix: remove the attribute from manifest. .
      2. In the App element, the Genre attribute changed from NormalApp to Apps.Normal
      3. In the App element, the RuntimeType attribute changed from SilverLight to Silverlight.   {notice the case difference on the L}
    10. System.Reactive namespace was moved to Microsoft.Phone.Reactive and there is a new assembly called Microsoft.Phone.Reactive.dll
      The System.Concurrency and System.Disposable namespaces are now on this assembly.
    11. Removed ManipulationCompletedEventArgs.IsTapEvent property
      To fix: remove references to the property.  You can use OnClick handlers.
    12. Choosers API are no longer returning on OnChooserReturn; they now have instance based events.
      The PhoneNumberChooserTask and PhotoChooserTask used to always return on an override on the Page hosting. They now have events that the task exposes and you can add the handler and listen to the return from any class you like.  The tasks have a Completed event you can listen to.
    13. Other miscellaneous changes you might run into:
      1. The ApplicationBarIconButton has a new Text property and it can't be empty.. whitespace does not work either.
        You must enter something; old projects will get an InvalidOperationException with message of “Text cannot be empty” if you try to use the buttons with no text.  
        To fix:  enter some text for your button.
      2. OnOrientationChanging virtual method on PhoneApplicationPage has been removed; now you just get OnOrientationChanged
        To fix: remove references to Deprecated event
      3. Signature change on PhoneApplicationPage class
        override void OnNavigatedFrom(Microsoft.Phone.Navigation.PhoneNavigationEventArgs e)  becomes
        override void OnNavigatedFrom(System.Windows.Navigation.NavigationEventArgs e);
      4. AccelerometerReadingEventArgs was refactored and it no longer has a Value property wrapping the sensor data; you can now get to the X, Y, Z properties directly.
      5. I already mentioned that WindowsPhoneEvents class was replaced by  PhoneApplicationService class. 
        The events in these classes were also renamed from Paused to Deactivated and Resume to Activated.
      6. System.Windows.Browser.dll  has been finally removed. You should not have been using this assembly (since nothing worked, it was not supported).   The common reasons to look for this assembly included:
        HttpUtility class, which is in System.Windows.dll in the System.Net namespace.
        Interop between browser and Javascript.  If you are needing that, use the WebBrowser control and the ScriptNotify and
      7. Changed PhoneApplicationPage.ApplicationBar property from type ApplicationBar to IApplicationBar.
        Your old code should still work, but there might be a cast needed pending how you coded it.
      8. ManipulationDeltaEventArgs.CumulativeManipulation.Translation, ManipulationDeltaEventArgs.DeltaManipulation.Translation, and ManipulationDeltaEventArgs.TotalManipulation.Translation are now only populated when the user has moved certain number of pixels from original contact point.
      9. Scale property in ManipulationDeltaEventArgs.DeltaManipulation and ManipulationDeltaeventArgs.TotalManipulation has been changed to return 1 when there is no changes (instead of returning 0 in CTP Refresh); this mostly means you can go through your code, and remove the check you would have had to add before where you were detecting 0.0 and throwing it away.
    14. A few push notification changes (this list is best explained via sample,  the new TrainingKit has a great hands-on lab that will walk you through these changes )
      1. Microsoft.Phone.Notification went away, reference Microsoft.Phone
      2. Channel.ExceptionOccurred event is now Channel.ErrorOccurred
      3. HttpNotificationChannel.BindToShellNotification is now HttpNotificationChannel.BindToShellTile ()
      4. NotificationChannelExistsException has been removed.  You can now check if HttpNotificationChannel.IsShellTileBound before you bind to Shell
      5. ShellEntryPoint class is gone. We not use standard Uris
      6. HttpNotificationChannel.ShellNotificationReceived is now HttpNoficationChannel.ShellToastNotificationReceived 

    Steps for migrating your code (referenced above as MigrationTips.X ) :

    1. Fix all project references. 
      1. Remove references to Microsoft.Phone.Controls, Microsoft.Phone.Controls.Navigation, Microsoft.Phone.Controls.WebBrowser, Microsoft.Controls.WebBrowser.interop
      2. Add a reference to Microsoft.Phone assembly
      3. Do a global search and replace for the xmlns declarations. For example:
        Search for Replace with
        clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Navigation clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone
        clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.WebBrowser clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone
        clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone.Shell clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone
        clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone
           

        The list above is not all inclusive, but it is the most common one and shows you the “pattern” to use: don’t replace the whole xmlns declaration, just replace the clr-namespace strings, and your code will be easier to migrate.  The search above can be applied to just XAML files, making it slightly quicker to replace all.
    2. Fixing themes and App.xaml resources issues:
      Assuming you had not added your own resources,
      1. open App.xaml and remove all the Application.Resources
      2. Do a global find/replace for following strings, these are resources that were in App.xaml but are not automatically inserted by the run-time. Most of these are used by the default Mainpage.xaml that every new project includes:
    3. Search for Replace with:
      PhoneTextPageTitle1Style  PhoneTextNormalStyle
      PhoneTextPageTitle2Style PhoneTextTitle1Style
      PhoneTextApplicationNameStyle PhoneTextNormalStyle
      PhoneTextTitleNameStyle PhoneTextTitle1Style
    4. Fixing your WMAppManifest.xml file
      1. The easiest way to fix your manifest is to create a new empty project with same name than your existing one, and copy the whole WMAppManifest to your project.  You can also copy a few of the attributes, elements. These are the commone ones to watch out for:
      2. XNA projects used to have an PlaceHolderString="Default task" on the WMAppManifiest.xml  this XML attribute is not longer valid, please remove it.
      3. In the App element, the Genre attribute changed from NormalApp to Apps.Normal
      4. In the App element, the RuntimeType attribute changed from SilverLight to Silverlight.   {notice the case difference on the L}
      5. Do clean-up or update the auto-generated metadata on your WMAppManifest, including App.Author, App.Description and App.Publisher
      6. If you do not have capabilities (maybe you were not using refresh but MIX build) copy the whole  <App><Capabilities> element from the empty project to yours
      7. <Tasks> collection has a <DefaultTask> element with a new NavigationPage attribute.  In previous builds this was set via code in App.xaml via the <Application.RootVisual> you can use either. They both work, just make sure you have one or the other approach (and not both)
    5. Remove old workarounds:
      1. Remove checking for 0 == ManipulationDeltaEventArgs.*.Scale.
      2. TransformToVisual and FindElementsInHostCoordinates. Orientation has been fixed and now we rotate the application frame, so these workarounds are no longer needed.
      3. You can now reference signed assemblies again, so if you had used the workaround to unsign them, you can remove it again.

    Closing Advise/asks:
    Please share back your experiences and tips!!    I know this document is not 100% comprehensive. If you run into one change that I missed or get stuck along the way, please email me directly [via email blog author on the sidebar column] so we can update and improve this document.

    Don’t forget to read the release notes. This document overlaps with some of the content in the release notes, but I focused on migration, so there are other tips and known issues there that will be useful to you; please take a look at the release notes. 

    Happy Windows Phone coding!

  • Jaime Rodriguez

    WPF ribbon has been released..

    • 6 Comments

    Congratulations to the WPF team in shipping the WPF ribbon control…

    You can download it, from:
    http://www.microsoft.com/downloads/details.aspx?FamilyID=2bfc3187-74aa-4154-a670-76ef8bc2a0b4&displaylang=en


    Highlights from their announcement:

    · New Controls – We have numerous new controls in this release of Ribbon such as, RibbonGallery, RibbonComboBox, RibbonMenuButton, RibbonSplitButton, QuickAccessToolbar, etc.

    · MVVM – Ribbon now facilitates MVVM-centric applications

    · KeyTips – The new Ribbon now implements this new accessibility feature to allow full control using the keyboard

    · Resizing – Our resizing approach has come a long way since the CTP version.  We have default logic that resizes controls inside the Ribbon to render the best visual appearance and orientation

    · Design-time – This release includes design-time support for Visual Studio 2010 and Expression Blend 4. This includes a project template for a ‘WPF Ribbon Application’, an item template for a ‘Ribbon Window’, and Ribbon controls in the toolbox such as, RibbonButton, RibbonToggleButton, RibbonMenuButton, etc.


    I have been heads down on phone for the last four months, so I have not played with ribbon recently, but they had every thing they describe above back then, so enjoy it!!

    By the way, they also relaxed the licensing;  the control it will be in the platform in the next WPF release, and does not require Office UI licensing.


    Happy ribbon coding!!    If you create a cool demo please let me know or leave it on the comments. 

  • Jaime Rodriguez

    Datagrid (part3): styling.

    • 6 Comments

    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. 

    datagridFinal

    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>
    I also wanted Alternating rows, so I set AlternateCount to 2 in the datagrid and then I created a trigger for the RowStyle.
    <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>
    and of course apply this on the datagrid CellStyle='{StaticResource CellStyle}'
    Voila!
    datagridhighlight 
    In the usual WPF way, styles and templates allow incredible flexibility and leave the designer in control for a visually stunning look with no code.
    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. 

    Blend

    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.

  • Jaime Rodriguez

    Drag &amp; Drop in WPF ... Explained end to end ..

    • 6 Comments

    How to do Drag& Drop in WPF is a question I hear often... 
    I have seen some great samples out there, but most focus on either a big code sample or a niche scenario...  a couple of times I have ended up having to help some one who got stuck.
    I hope the below write up is useful to explain the steps and decisions to get drag & drop done.. and it comes with sample snippets ...

    -- ---- -------------------
    From  [http://msdn2.microsoft.com/en-us/library/aa289508(vs.71).aspx]  Here is the sequence of events in a typical drag-and-drop operation:

    1. Dragging is initiated by calling the DoDragDrop method for the source control.

      The DoDragDrop method takes two parameters:

      • data, specifying the data to pass
      • allowedEffects, specifying which operations (copying and/or moving) are allowed

      A new DataObject object is automatically created.

    2. This in turn raises the GiveFeedback event. In most cases you do not need to worry about the GiveFeedback event, but if you wanted to display a custom mouse pointer during the drag, this is where you would add your code.
    3. Any control with its AllowDrop property set to True is a potential drop target. The AllowDrop property can be set in the Properties window at design time, or programmatically in the Form_Load event.
    4. As the mouse passes over each control, the DragEnter event for that control is raised. The GetDataPresent method is used to make sure that the format of the data is appropriate to the target control, and the Effect property is used to display the appropriate mouse pointer.
    5. If the user releases the mouse button over a valid drop target, the DragDrop event is raised. Code in the DragDrop event handler extracts the data from the DataObject object and displays it in the target control.

    ------------

    Let's walk through it in WPF...

    Detecting Drag & Drop.

    Before the DoDragDrop is called, we must detect a mouse Drag operation on the source...  A mouse drag is usually a MouseLeftButtonDown + a MouseMove (before MouseLeftButton goes up) ...

    So, our drag & drop source control needs to subscribe to these two events:

    void Window1_Loaded(object sender, RoutedEventArgs e)
         {
             this.DragSource.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(DragSource_PreviewMouseLeftButtonDown);
             this.DragSource.PreviewMouseMove += new MouseEventHandler(DragSource_PreviewMouseMove);
         }

    To prevent from starting a false drag & drop operation where the user accidentally drags, you can use SystemParameters.MinimumHorizontalDragDistance  and SystemParameters.MinimumVerticalDragDistance

    One way to do this is on MouseLeftButtonDown, record the starting position  and  onMouseMove check if the mouse has moved far enough..

            void DragSource_PreviewMouseMove(object sender, MouseEventArgs e)
            {
                if (e.LeftButton == MouseButtonState.Pressed && !IsDragging)
                {
                    Point position = e.GetPosition(null);
    
                    if (Math.Abs(position.X - _startPoint.X) > SystemParameters.MinimumHorizontalDragDistance ||
                        Math.Abs(position.Y - _startPoint.Y) > SystemParameters.MinimumVerticalDragDistance)
                    {
                      StartDrag(e); 
    
                    }
                }   
            }
    
            void DragSource_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                _startPoint = e.GetPosition(null);
            }
    
     

     

    Its a Drag .. now what?

    The data!  You need to find out what is under the mouse when dragging.
    I will omit take the easy way out and assume that whoever is triggering the MouseMove is what I want to drag .. so look at MouseEventArgs.OriginalSource..   [or you could do some 2D HitTesting using VisualTreeHelper .. In Part3 of this write up will try to walk you through hit testing the listbox -which is the other common scenario I encounter-.

    Once you have the object to drag, you will need to package what you are a sending into a DataObject that describes the data you are passing around. 
    DataObject is a wrapper to push generic data (identified with extensible formats) into drag/drop..  As long as both the source and destination understand the format, you will be set.  As such, DataObject has a couple interesting methods:

    • SetData (  Type format, object data )    /// format is the "format" of the day you are passing ( e.g. Formats.Text,  Formats.Image, etc.. ) you can pass any custom types.
    • GetDataPresent (  Type format )  /// is what the drop target will use to inquire and extract the data .. if it is a type it can handle, it will call GetData () and handle it ..

    Not much interesting stuff here..  In the sample I just hard-coded my data to be of type string... this makes it easier to paste into external containers (for example Word, which you can use to test this part of the write-up).   I do have to stress that drag & dropping should be about the data ... 

    Providing visual feedback during the drag & drop operation..

    Before we call DoDragDrop () we have a few 'choices' to make around the feedback we want to provide and the 'scope' of the d&d.  

    • Do we want a custom cursor to display while we are doing the Drag operation ?  If we want a cursor, what should it be??
    • How far do we want to drag?? within the app or across windows apps?

     

    Simplest scenario:  No custom cursor and we want it to drag across apps: 

    If you don't want a fancy cursor, you are done!!  You can call DoDragDrop directly ...

     private void StartDrag(MouseEventArgs e)
            {
                IsDragging = true;
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
                IsDragging = false;
            }

    Note: this code allows you to drag & drop across processes, it uses the default operating system feedback ( e.g. + for copy).. 

     

    Next scenario: We want a pre-defined custom cursor...    

    Say we had a .cur file and embedded it on to our application as a resource ( see sample code).   We can subscribe to GiveFeedback () and wire our cursor there..

    private void StartDragCustomCursor(MouseEventArgs e)
            {
    
                GiveFeedbackEventHandler handler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
                this.DragSource.GiveFeedback += handler; 
                IsDragging = true;
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
                this.DragSource.GiveFeedback -= handler; 
                IsDragging = false;
            }

    Our handler for feedback looks like this:

    void DragSource_GiveFeedback(object sender, GiveFeedbackEventArgs e)
            {
                    try
                    {
                        //This loads the cursor from a stream .. 
                        if (_allOpsCursor == null)
                        {
                            using (Stream cursorStream = System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream(
    "SimplestDragDrop.DDIcon.cur")) { _allOpsCursor = new Cursor(cursorStream); } } Mouse.SetCursor(_allOpsCursor); e.UseDefaultCursors = false; e.Handled = true; } finally { } }

    Two things to notice: 
    1) I cached the cursor...  GiveFeedback will be called many times  as the mousemoves so I cached it..  and

    2) though I did not handle it, I called it "_allOpsCursor" because GiveFeedbackEventArgs will tell you the possible operation for the cursor (e.Effects)...  I could have used multiple cursors, one for each effect.

     

    Next scenario: Getting fancy and using the Visual we are dragging for feedback [instead of a cursor]

    The first thing you will need is to an Adorner; in my case I chose and adorner that contains a VisualBrush of the Element being dragged...      you can go with RenderTargetBitmap, or possibly reparent the object directly ... but I like VisualBrush in case the drag is cancelled..  

    The constructor for the adorner class is where most of the action happens:

    public DragAdorner(UIElement owner, UIElement adornElement, bool useVisualBrush, double opacity)
                : base(owner)
            {
                _owner = owner;
                if (useVisualBrush)
                {
                    VisualBrush _brush = new VisualBrush(adornElement);
                    _brush.Opacity = opacity;
                    Rectangle r = new Rectangle();
                    r.RadiusX = 3;
                    r.RadiusY = 3;
                    r.Width = adornElement.DesiredSize.Width;
                    r.Height = adornElement.DesiredSize.Height;
    
                    XCenter = adornElement.DesiredSize.Width / 2;
                    YCenter = adornElement.DesiredSize.Height / 2;
    
                    r.Fill = _brush;
                    _child = r;
    
                }
                else
                    _child = adornElement;
    
            }

     //There is more code in DragAdorner, but mostly used for positioning the adorner as the drag is happening... please refer to the sample...

     

    Now, that we have our custom adorner ready, the tricky part is wiring it so it follows the cursor position.  There are two options here:

    • If we want drag & drop across apps, we are going to have to call Win32's GetCursorPos () ...   This is trivial to write but requires full-trust ...  (which you likely had if you needed to drag & drop with other apps anyway )...
    • If we want to drag & drop inside our app only or inside a specific 'scope' with in the app, there is a hucky workaround that I often use to avoid the interop code..

     

    Using Visual for Feedback.1 : D&D across apps using GetCursorPos () ... 

    First we have to import Win32's code using DllImport ....  [trivial stuff, refer to sample code in Win32.cs ]

    Next we create an instance of a Window, which will contain a visual brush of the element we are dragging ... 

    private Window _dragdropWindow = null;
            private void CreateDragDropWindow(Visual dragElement)
            {
                System.Diagnostics.Debug.Assert(this._dragdropWindow == null);
                System.Diagnostics.Debug.Assert(dragElement != null);
                // TODO: FE? or UIE??   FE cause I am lazy on size . 
                System.Diagnostics.Debug.Assert(dragElement is FrameworkElement); 
    
                this._dragdropWindow = new Window();
                _dragdropWindow.WindowStyle = WindowStyle.None;
                _dragdropWindow.AllowsTransparency = true;
                _dragdropWindow.AllowDrop = false;
                _dragdropWindow.Background = null;
                _dragdropWindow.IsHitTestVisible = false;
                _dragdropWindow.SizeToContent = SizeToContent.WidthAndHeight;
                _dragdropWindow.Topmost = true;
                _dragdropWindow.ShowInTaskbar = false;
    
                _dragdropWindow.SourceInitialized += new EventHandler(
                delegate(object sender, EventArgs args)
                {
    
                    //TODO assert that we can do this.. 
                    PresentationSource windowSource = PresentationSource.FromVisual(this._dragdropWindow);
                    IntPtr handle = ((System.Windows.Interop.HwndSource)windowSource).Handle;
    
                    Int32 styles = Win32.GetWindowLong(handle, Win32.GWL_EXSTYLE);
                    Win32.SetWindowLong(handle, Win32.GWL_EXSTYLE, 
    styles | Win32.WS_EX_LAYERED | Win32.WS_EX_TRANSPARENT); }); Rectangle r = new Rectangle(); r.Width = ((FrameworkElement)dragElement).ActualWidth; r.Height = ((FrameworkElement)dragElement).ActualHeight; r.Fill = new VisualBrush(dragElement); this._dragdropWindow.Content = r; // put the window in the right place to start UpdateWindowLocation(); }

    Notice:

    1) I set the style to Transparent, layered window (this is ok since the window is small and it is only used for drag & drop )..  and 

    2) the call to UpdateWindowLocation () this is the code that positions the Window wherever the cursor is now..

    3) I likely need more error checking  

    The code in UpdateWindowLocation is straight forward:

     void UpdateWindowLocation()
            {
                if (this._dragdropWindow != null)
                {
                    Win32.POINT p;
                    if (!Win32.GetCursorPos(out p))
                    {
                        return;
                    }
                    this._dragdropWindow.Left = (double)p.X;
                    this._dragdropWindow.Top = (double)p.Y;
                }
            }

    This UpdateLocation code of course needs to be called whenever the cursor moves...  so we need some kind of callback during the drag operation.. We will use QueryContinueDrag for that..

    So, I go back to  the code in StartDrag ()  and wire up the event, as well as some code to show the window and destroy it after drag & drop:

     private void StartDragWindow(MouseEventArgs e)
            {
    
                GiveFeedbackEventHandler feedbackhandler = new GiveFeedbackEventHandler(DragSource_GiveFeedback); ;
                this.DragSource.GiveFeedback += feedbackhandler; 
                QueryContinueDragEventHandler queryhandler = new QueryContinueDragEventHandler(DragSource_QueryContinueDrag);
                this.DragSource.QueryContinueDrag += queryhandler; 
                IsDragging = true;
                CreateDragDropWindow(this.dragElement); 
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                this._dragdropWindow.Show(); 
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
                DestroyDragDropWindow(); 
                IsDragging = false;
                this.DragSource.GiveFeedback -= feedbackhandler;
                this.DragSource.QueryContinueDrag -= queryhandler; 
            }

    The one thing to notice is that I still have GiveFeedbackHandler wired.. Why ?? We are no longer using the cursor...  but we still have to tell Drag & Drop not to use the default cursors..

     

    Using Visual for Feedback.2: Using DragOver to avoid the interop code and/or to limit dragging scope to my app.. 

    There is a slightly different approach you can use if you are drag & dropping just inside your app or have a smaller scope ... I some times use this approach because it allows me to avoid interop, avoid creating extra windows, and better control the scope of the drag...  


    Here is the full explanation of how it works and why it feels like hackalicious.

    When you call DoDragDrop, there is no Mouse or Cursor Events being fired in your WPF app...  OLE does the work for you and it moves cursor directly :(...  however, all of the Drag events are being fired... 

    We already know of the two events we can tap into from the source: GiveFeedback and QueryContinueDrag...    however neither of these events gives us access to the mouse or cursor position during the drag operation :( ...   We can however tap into the  Dragover  event; DragOverEventArgs has a GetPosition ( ) method that does the trick...    DragOver however is fired in the target, not the source.

    So, how would we do it??  Well , DragEvents are routed events.. they bubble up.. if we define a "Drag Scope"  within our app that we know is guaranteed to bubble the DragOver, then we can listen for it ...   the obvious choice for that scope is our Application's Window; this gives us access to any thing in our app; the scope could be smaller of course... 


    Here is how we wire that: 

    private void StartDragInProcAdorner(MouseEventArgs e)
            {
    
                // Let's define our DragScope .. In this case it is every thing inside our main window .. 
                DragScope = Application.Current.MainWindow.Content as FrameworkElement;
                System.Diagnostics.Debug.Assert(DragScope != null);
    
                // We enable Drag & Drop in our scope ...  We are not implementing Drop, so it is OK, but this allows us to get DragOver 
                bool previousDrop = DragScope.AllowDrop;
                DragScope.AllowDrop = true;            
    
                // Let's wire our usual events.. 
                // GiveFeedback just tells it to use no standard cursors..  
    
                GiveFeedbackEventHandler feedbackhandler = new GiveFeedbackEventHandler(DragSource_GiveFeedback);
                this.DragSource.GiveFeedback += feedbackhandler;
    
                // The DragOver event ... 
                DragEventHandler draghandler = new DragEventHandler(Window1_DragOver);
                DragScope.PreviewDragOver += draghandler; 
    
                // Drag Leave is optional, but write up explains why I like it .. 
                DragEventHandler dragleavehandler = new DragEventHandler(DragScope_DragLeave);
                DragScope.DragLeave += dragleavehandler; 
    
                // QueryContinue Drag goes with drag leave... 
                QueryContinueDragEventHandler queryhandler = new QueryContinueDragEventHandler(DragScope_QueryContinueDrag);
                DragScope.QueryContinueDrag += queryhandler; 
    
                //Here we create our adorner.. 
                _adorner = new DragAdorner(DragScope, (UIElement)this.dragElement, true, 0.5);
                _layer = AdornerLayer.GetAdornerLayer(DragScope as Visual);
                _layer.Add(_adorner);
    
    
                IsDragging = true;
                _dragHasLeftScope = false; 
                //Finally lets drag drop 
                DataObject data = new DataObject(System.Windows.DataFormats.Text.ToString(), "abcd");
                DragDropEffects de = DragDrop.DoDragDrop(this.DragSource, data, DragDropEffects.Move);
    
                 // Clean up our mess :) 
                DragScope.AllowDrop = previousDrop;
                AdornerLayer.GetAdornerLayer(DragScope).Remove(_adorner);
                _adorner = null;
    
                DragSource.GiveFeedback -= feedbackhandler;
                DragScope.DragLeave -= dragleavehandler;
                DragScope.QueryContinueDrag -= queryhandler;
                DragScope.PreviewDragOver -= draghandler;  
    
                IsDragging = false;
            }
      

    Explanations:

    • GiveFeedback is the same than before we use it to set no default cursor ..
    • Dragover on our DragScope  is what will let us move the cursor around..  These events are wired in the Drop target, not in the source control..
    • DragLeave is optional; the reason I wired it is because when the mouse leaves the scope, I want to cancel the Drag operation altogether, nix it!  So I subscribe to DragLeave to know when mouse left.. Unfortunately, I can't cancel the drag in DragLeave, so I set a flag to be read in QueryContinueHandler. QCH reads this flag and when set to true,  it sets the Action to Cancel in the drag to nix it..
    • The rest is creating our adorner, and the drag drop ..  plus all the clean up ... 

    There is a common gotcha with the DragLeave part of this scenario. The scope tends to always be a panel,grid, etc.. ( a container) and if the container has background to null, it is not hittesting, so you won't get the dragleave...   You have to explicitly set the Background="Transparent" to make sure you get it...   (you can see it in my sample with the Grid)..

    That is it for Drag ...   I hope I explained how to do the Drag part  of a drag  & drop.     I want to cut part 1 here so that you have a pretty clean sample of the "drag" .. 

    The source for every thing above is here.   

     

    You will have to tweak the MouseMove function to select which drag approach to use.. Just make sure you have at most one of these functions uncommented at any time..

    // StartDrag(e);
    //  StartDragCustomCursor(e);
    // StartDragWindow(e);
    StartDragInProcAdorner(e);

     

    Since I did not wire a Drop handler, for testing this, just "Drop" into some thing that handles Text like Microsoft Word..

    In part 2, I will cover the drop .. and in part 3 I will share the complete code with a couple of extra things that I omitted here to try to keep it clean  (some of them might be in the code sample)

  • Jaime Rodriguez

    tweaking your wpf code to run in Expression Blend...

    • 5 Comments

    beta 1 of Expression Blend ( aka Expression Interactive Designer, codename Sparkle)  went out  a month ago...     I hope you are using it now..  I have used it for a while beforexe it went out, it has been very useful for creating animations, templates, styles, etc.. 

    If you have tried to use Blend with existing ( arguably very large) projects coded prior to Blend, you might have seen 'errors' or even crashes when opening your project in blend;  don't give up yet! imho blend is not too unstable, once you understand their design-time rules..    (read below to learn some of these)..

    Background:  
    Blend is a WPF application and as such it runs all user interface in a single AppDomain;  there fore when you design your app in blend, it tries to load [and run] your code in their executable... so be careful what you feed it :( ...   

    How and why does it run your code?
    There are two instantiation patterns i see in expresssion:

    • Blend will instantiate the base type of the root element on the design surface.. so for your Window, or UserControl or similar, it is mostly OK..
    • If you have a UserControl (UC) or Custom control you are droppping into a root document ( the root element described above)..  then Blend instantiates YOUR control, and at the minimum runs your constructor ....  
      • if inside your constructor you have code that subscribes to any other events, like   Initialized,  Loaded, or CompositionTarget.Rendering  then you are already telling the UI to call you back for this events [so yet more code to run] ...  if any of your code throws an exception, Blend will throw an error...

    Here is an example:  comments in D00BE8

    <!--  This is what I referred to above as "root element", here it instantiates Window class instead of  your Window1 class, so not your code -->
    <
    Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ExpresssionTests="clr-namespace:ExpresssionTests" x:Class="ExpresssionTests.Window1" Title="ExpresssionTests" Height="377" Width="362">
    <
    Grid>
    <!-- Here because you are inside the document (in this case window, it will instantiate your control directly, running the constructor for UserControl1, myTextBox and MyPanel  it does not matter if they are user controls or custom controls  -->
    <
    ExpresssionTests:UserControl1 Margin="32,51,161,0" VerticalAlignment="Top" Height="108"/>
    <
    ExpresssionTests:MyTextBox Margin="32,21,0,0" VerticalAlignment="Top" Height="26" Text="MyTextBox" TextWrapping="Wrap" HorizontalAlignment="Left" Width="113"/>
    <
    ExpresssionTests:MyPanel Margin="42,0,161,58" VerticalAlignment="Bottom" Height="92"/>
    </
    Grid>
    </Window>

    So, it calls your constructor (and executes your code).. why should you care? Your app works great when launched stand-alone (or debugger in VS).. 

    For a lot of your code, this will be fine...   for that slight percentage that it is not find, there are a few possible bad outcomes from it running your code:

    • Your code does some thing that throws an unhandled exception and crashes Blend..
    • Your code does some thing that throws an exception and blend handles it, but you get a generic error like "Can not create instance of UserControl1"
    • Your code might appear to be running OK in blend but it is consuming resources it should not ( e.g. if you are designing a Video Player, and in your player, you are telling it to "autoplay" when loaded,  so now you have a video playing at design-time.. [this is not only annoying but consuming resources, so your system slows down :( ]

    "Jaime, did you not read that 'my code works fine, I don't expect crashes.' "
    Hear ya, but there are a few valid scenarios where your perfect code behaves different when run from within blend.. here are a few commone ones:

    • Your application is data-driven and your control assumes the context and data models is available at design-time, but it is not ...  or your app tries to connect to its datasource and it fails ..
    • Your application uses the pack syntax ... [ I will come back to this]
    • Your code tries to access the global Application class or it's MainWindow (but since you are running in Blend, you get Blend's Application class)..

    "OK, I get it, how can I get around this?.."

    Blend and Cider team added a System.ComponentModel.DesignerProperties class to WPF so you can query if you are in 'design mode'.. 
    You can call System.ComponentModel.DesignerProperties.GetIsInDesignMode( DependencyObject element )  with any valid DependencyObject -translated pretty much any visual- , and find out if you are in the designer; 

     if you don't have a DependencyObject ( e.g. if you are in your business object and don't want to couple if with UI) then
    ((bool)DependencyPropertyDescriptor.FromProperty(DesignerProperties.IsInDesignModeProperty, typeof(DependencyObject)).Metadata.DefaultValue)  
     can do the trick to tell you if you are in design-mode.. even when you don't have a visual * ..

    <sideline> Jim Nakashima and Brian Pepin are good sources for info in this design mode property </sideline>

    "This sounds messy!  I don't want to be crowding code with design-time checks ... what should I do?"
    At first, I thought that too ... but it is really not too bad.. in fact, now I would argue it has made my code slightly better: I used to have projects where there was a bit more code than needed in the constructor.. [this code should have been in the Initialized or Loaded events] ... so you just need to refactor your code a little bit..  you can then use the design-time check to possibly not wire these other while in design-time...

    Another lazy workaround I used for those more complex projects with lots of transitions where I did need code in the constructor or had too much code and did not want to compromise run-time code readability w/ design-time checks was a put a very big  try { } catch in the  constructor or event being called...   in the catch, I check if I am in design time and just not bubble ( re-throw ) the exception ....  not ideal but it does the trick.. e.g.

     try
                {             
    /// Lots of code would go here...  as long as it gets caught below Expression will be fine.. 
                }
                catch (Exception ex)
                {
    // I like to log them so that  I can clean up once a week..
    // This will no longer be necessary soon... Blend  will start letting you see the stack traces in the next public build  
    // it also re-throws if this is run-time
                  ExpressionWorkAround.DesignTimeHelper.LogException(ex);
                }

    "... the explanation is fine, but I can't even open my project in Blend... I don't think it is getting far enough to run any code.. " ... 
    Almost every time I think this, I look and it is getting far enough,  here is what happens:  Blend opens the solution and then it starts opening the windows I had opened last time in the designer, and crashes while executing my code..  Here are a few tips on opening projects:

    1. You can pass your full project path to blend from command-line ...  when you do this, blend simply opens the project/solution.. It does not automatically open any of your Windows (or scenes) in the designer  ... you will just need to double click in the window/control and it will then open it in the designer [possibly reproducing the crash] ...

      c:\Program Files\Microsoft Expression\Blend Beta 1\Blend.exe "c:\mypath\myproj.csproj"

    2. If you open blend from command line, you can pass the /exceptionlog parameter and if it crashes, it will show you the exception's call stack ...  so you know where to look. This is of course useful even if your project is not crashing when opening.. it will log the exception when ever it happens..

    c:\Program Files\Microsoft Expression\Blend Beta 1\Blend.exe /logException   
    Then you open your project and run as usual ...

    "OK., I am getting it .. any more tips?"
    Yes, but getting late and I am going to bed :( so here is the last one..   as mentioned Blend is a WPF app and it loads your code in it.. so  if you are getting design time errors and want to troubleshoot you can open good old Visual Studio, open your project in both VS and blend, and from VS attach to blend and debug, stepping through it, etc...  pretty useful for complex stuff .. 

    1. Open your project in Blend
    2. Open your project in VS ..
    3. From VS attach to Expression Blend ... (Attach Process, proc name is blend.exe ) ...
    4. [Optional] In VS, tell it to break on any thrown Exceptions.. 
      1. via  the Menus: Debug -> Exceptions
      2. Check the "thrown" box i nthe Common Language  Runtine exceptions
    5. [Optional] if don't want to stop at all exceptions, then put a breakpoint in your code..
    6. Now, in Blend close & open the scene (window, user control) you want to debug.
    7. If you are not getting it to hit your breakpoint, some times I have to do a Rebuild all in blend... before it catches on ..

    One last remark... 
    if you are a designer, or new to Blend and reading this, don't freak yet...   for designers, most of this stuff above happens when devs start putting code in places they might not need to so you are likely not to run into it...  for new projects also, it might not be an issue.. it is  those large projects that have 10s of thousands of lines of code in hand-coded XAML and or C# code...   Also, note that a lot of the troubleshooting tips above get much easier in later versions of Blend (I think beta2)... I mostly wanted you to get the behavior and how blend loads your code.. so you understand what up...

    I will try to come back w/ a blend tips & tricks ...   In the mean time, there is a small project with some sample code to illustrate points above here..   Do a search for //TODO: interesting  comments .... you can check them out, run the code, etc...   if you run it simultaneously w/ dbgview, you should be able to see the Debug.WriteLine ()s  that tell you a little of what's going on ...

     Good luck ...

    * [disclaimer, I should credit some one for the tip on using IsInDesignMode dep property with out a dep object, but can't recall who it was that shared this.. my guess would have been Unni or John Gossman. Sorry!  Do check their blogs though :)

     

     

     

Page 1 of 9 (222 items) 12345»