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

  • 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

    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

    forcing a WPF binding to 'refresh' ...

    • 2 Comments

    From an internal DL ... I thought this would be handy --- must admit is nicer than some hacks I have done in the past to accomplish same thing..

    ---  

    Sent: Tuesday, July 03, 2007 2:00 PM
    Subject: RE: forcing a rebuild of an items control...

     

    You’ll need to know the dependency object and dependency property where the binding was defined, which may not qualify as elegant.  Then you can use this line:

     

    BindingOperations.GetBindingExpressionBase(dependencyObject, dependencyProperty).UpdateTarget();

     

    --Eric

     

    Sent: Tuesday, July 03, 2007 1:08 PM
    Subject: forcing a rebuild of an items control...

     

    I have a control that has a list of stuff, and once I make a change to some unrelated thing I really need to force the binding to re-run… is there any elegant way to make this happen? Something like “InvalidateMeasure” but for binding… ?

     

  • 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

    Windows Phone Design Day Recordings

    • 5 Comments

    Windows Phone is getting very impressive reviews, and all of them mention the great, innovative user experience on the phone. JustPhone


    A few weeks ago, our Studios team, the folks responsible for the end-to-end user experience on the phone, invited a few partners and agencies for what we called “Windows Phone Design Days”. 
    This was a 1.5 day deep-dive into their inspiration, philosophy, goals, and down to the 9mm details on implementation.  
    We recorded this event. The videos have not been edited, but this was such great content we are rushing for you to get it, below, please find links to the recordings.

    If you are a developer or designer looking to target Windows Phone, this is a must-watch series.  If you hold any kind of role related to software visual and interactive design, this should still be a great series for you to watch; you will get amazing insights into the research and the process that our teams go through to build stunning, award-winning experiences like the ones you have seen in Zune HD, and will see in Windows Phone.

    I have organized them in the order we presented them, but you can watch them in any order you want.  Just make sure you watch them all.

    Ana and Miles, the Windows Phone personas (  Tracey Lovejoy, 3:33 )
    The Metro Design Language, the inspiration  ( Jeff Fong, 29:48)
    Deconstructing a Windows Phone application part 1: Controls  (  Rhon Manlapaz,  Ryan Bickel, 17:40)
    Deconstructing a Windows Phone application part2: Animation ( Jeff Arnold,  34:22 )
    Deconstructing a Windows Phone application, part 3: Target Sizes (  Tirthankar Sengupta,  13:39 )
    Deconstructing a Windows Phone application, part 4: Globalization (Ayman Raslan, Franklin Yow : 37:45 )
    Deconstructing a Windows Phone application, part 6: Perceived performance  (19:45 )
    Designer insights into Panorama and Pivot ( Chad Roberts, Amy Alberts, 32:18)
    Making Audio Sing on Windows Phone (Matthew Bennett, 34:26 )
    Windows Phone Voice ( Karen Kesler, 32:00 )
    Designer Resources: Expression Blend Overview and Roadmap ( Celso Gomes, Peter Blois, 41:20 )
    Designer Resources:  Windows Phone Documentation ( Chris Kilbourn, 11:18 )
    Designer Resources: Windows Phone Design Templates ( Chad Roberts, 04:01 )

    If after watching all these insightful videos you feel inspired and want to get started,  go check this post with links to all the designer reso urces for Windows Phone. 

    Happy Windows Phone coding!

  • 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

    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

    Forwarding the DataGrid’s DataContext to its’ columns..

    • 4 Comments

    DataContext is a very handy inherited property on any WPF application..     
    Most of the time, I set DataContext near the root on the [logical] tree, and let the inherited DataContext do its magic to bind the rest of the scene.

    I recently tried to bind a DataGridColumn to its inherited DataContext (via its datagrid container) and got a very surprising answer on the output trace window:

    “System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element…”

    What is happening here?   
    The Columns collection is just a property in the Datagrid; this collection is not in the logical (or visual) tree, therefore the DataContext is not being inherited, which leads to there being nothing to bind to.

    I needed the functionality so I had to create a workaround.. With out much thought I decided to: 

    1. Listen for DataContextChanged in the DataGrid
    2. When DataContext changes,  forward the new value to the DataGridColumns in the datagrid.
    3. Bind properties on the DataGridColumn to this ‘forwarded’ DataContext  ( as I originally intended)


    To get it done,  I did not inherit from DataGrid and create a new class..  Instead i used the richness of WPF’s property system to pull a 1,2 punch:

    1. Override DataGrid’s DataContext metadata and listen for changes in it…
    2. Add a FrameworkElement.DataContextProperty to DataGridColumn …

    Code looks like this:

    FrameworkElement.DataContextProperty.AddOwner(typeof(DataGridColumn));

    FrameworkElement.DataContextProperty.OverrideMetadata ( typeof(DataGrid),            
    new FrameworkPropertyMetadata
      
    (null, FrameworkPropertyMetadataOptions.Inherits,
       new PropertyChangedCallback(OnDataContextChanged)));

    The OnDataContextChanged callback simply forwards the DataContext from DataGrid to its columns:

    public static void OnDataContextChanged ( DependencyObject d, DependencyPropertyChangedEventArgs e)
    { 
        DataGrid grid = d as DataGrid ; 
        if ( grid != null  ) 
        {                 
            foreach ( DataGridColumn col in grid.Columns ) 
            { 
                col.SetValue ( FrameworkElement.DataContextProperty,  e.NewValue ); 
            } 
        } 
    }


    That is it. Now we are ready to databind to DataGridColumns.

    <dg:DataGridTextColumn Binding="{BindingA}"
                       
    Visibility="{Binding ElementName=ShowA,Path=IsChecked ,
                      
    Converter={StaticResource BoolToVisConverter}}" />

     

    You can download source code for a small sample from here.

    DataGridColumnsAll
    The project has 3 checkboxes that are databound to a viewmodel…    The DataGrid’s column Visibility is databound to this same viewmodel,  if the checkboxes are checked ,the respective column is visible.. if unchecked, it is collapsed..
    DataGridColumnsnob


    A few more thoughts on DataGridColumn not being in the tree ..

    1. Binding to other UIElements via ElementName will not work because there is no tree.  
    2. Binding to a  StaticResource works fine..
    3. Binding to x:Static  will work fine too.
       

    Happy datagrid coding..  Again, there is source here.

  • 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

    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 & Drop in WPF.. part 3 .. the results and code...

    • 3 Comments

    Here is where we take every thing learned in Part1 (the drag) and Part2  (the drop)  and package it into more realistic scenarios...  

    Let's first begin by walking through a few items we skipped in earlier parts for brevity:

    Detecting DragDropEffects...

    When you start a drag, you should check if Ctrl  or Shift  are pressed...    Each one of these being pressed implies a different action for our drag operation...

     DragDropEffects effects = DragDropEffects.None;
    bool ctrl = Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl);
    bool shift = Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown(Key.RightShift);
    
    if (ctrl && shift && AllowsLink)
            effects |= DragDropEffects.Link;
    else if (ctrl && AllowsCopy)
           effects |= DragDropEffects.Copy;
    else if (AllowsMove)  // the default is to move in my sample .. 
           effects |= DragDropEffects.Move;

     

    Detecting what needs to be dragged:

    Deciding what needs to be dragged is sucky because you almost always have to put app knowledge into your drag & drop code..   There is not a one size fits all..

    For example:  <Button Content="Test" />    will generate a tree that looks like:     Button - > Chrome - > Content Presenter - > TextBlock   ...
    So, if you hitTest on what is visible, you might get the  TextBlock  when what you wanted was the button ....


    I don't have a great answer for this. As I mentioned, my approach is to lazily shoot for the OriginalSource element in the MouseMove event handler..   This seems to work slightly better than doing HitTesting (where I could get an item lower in the tree like the TextBlock ..

    For those that do want to do hittesting, the code is trivial:

     UIElement GetDragElementFromHitTest(UIElement dragSourceContainer, MouseEventArgs args)
            {
                HitTestResult hr = VisualTreeHelper.HitTest( dragSourceContainer, args.GetPosition((IInputElement) dragSourceContainer));
                return hr.VisualHit as UIElement;
            }

     

    Doing Drag & drop should be about data:

    I have to emphasize d&d was meant for data..   In the demo below I make the mistake of letting you drag UIElements and the like.. This is for illustration purposes... imho if you find yourself trying to drag any thing other than data, you should evaluate decoupling presentation and data.. 

     

    Now onto a real sample...  

    What I have done for my demos is create a library with:

    • DragHelper class:  given a source  (any UIElement) will subscribe to handle the required events to initiate a Drag& Drop operation.
    • DropHelper class: given a target ( any UIElement) will set properties needed  & subscribe to required events to complete a Drop operation...
    • DragAdorner -- same as previous examples, the class to draw the Adorner while element drags..

     

    I have also defined an interface that a class can implement to aid in the dragOperation...  

        public interface IDataDropObjectProvider
        {
            //Flag of actions sypported by implementation of  IDataDropObjectProvider
            DragDropProviderActions SupportedActions { get; }
    
            // Called before StartDrag () to get the Data () to be used in the DataObject 
            object GetData(MouseEventArgs e);
            // Called to get the visual ( UIElement visual brush of the object being dragged.. 
            UIElement GetVisual(MouseEventArgs e);
            // Gives feedback during Drag 
            void GiveFeedback(GiveFeedbackEventArgs args);
            // implements ContinueDrag -- to canceld the D&D.. 
            void ContinueDrag(QueryContinueDragEventArgs args);
            // called by the TARGET object .. this will attempt to "unparent" the current child so we can add it a child some where else.. 
            bool UnParent();
    
        } 
    This interface gets called back from either DragHelper or Drophelper  (whenever needed)
    A few things to notice: 
    • the interface is not necessarily implemented by either the source or the target. it is de-coupled from both.  Any other class can implement it and it can be generic. 
    • The interface is optional, if you do not implement it, the helpers classes will do their best to help..  My advise is always implement the GetData ( ) part of the interface. 
    • You can implement just part of the interface.  DragDropProviderActions is a flag that says what actions the interface supports.. 
     

    Wiring the 'helpers'

    Is pretty trivial ...   the constructor for DragHelper looks like this:

      public DragHelper(UIElement source, IDataDropObjectProvider callback, UIElement dragScope)
       // source is the drag source control ... 
       // callback is the interface  optional, pass null if you don't want to implement for call backs to GetData () , GetVisual(), etc.
      /*  dragScope is the hack we use if we are going to use DragOver...    Even though DragOver is a drop target event, we need to know about it in the drag ... because we have to wire up for it before calling DoDragDrop () ....   */

     

    Here are examples of using the constructors...    ( DropHelper I did not explain above beccuse the only parameter in is the drop target control itself..

    dragHelper = new DragHelper(this.canvas, null, null);
    dropHelper = new DropHelper(this.dropTargetPanel1 );
    
    dragHelperWScope = new DragHelper(this.canvas1,  null, (UIElement)this.BorderForScope);
    dropHelperWScope = new DropHelper(this.dropTargetPanel2);

    For the most part, these classes do what we discussed .. the exception is the drop..

    Common drop issues:

    Drop of data is straight forward...   there is no generic recipe for all scenarios but two that I use often are:  dropping to ContentControl or dropping to ItemsControl..  in which case I set appropriate property or add the data to the items collection ..

    Drop of UIElements is slightly trickier mostly because you need to re-parent your element to the target.. .  The sample classes use the IDataDropObjectProvider to Unparent () the UIElement before dropping it..

    Note that this assumes every thing is in process, the interface passes UIElements and other stuff around ...     To implement an Out of process handler for passing UI, you can make COM objects and pass that around..

     

    The sample

    Is not pretty ...  but quite functional. .. Here is the first half ..  

    image The app shows the two approaches:

    The Top grid 
    allows you to drag the rectangle and button any where you want...
    If you drag in the app to the TargetPanel, it will drag as UIElement
    If you drag in the app to the RichTextBox, it will drag as Data (Text, hardcoded "abcd" )..
    You can also use that to drag out of process ( Word is that I used)...
    You can drag Text from word into the RichTextbox  (duh ) and into the Target Panel ( Creates a TextBlock for you ) ...

    The bottom grid, does the same than top, but won't let you drag out of the Red border's scope...  It how ever does not limit the drop, so you can drop from any where ( word, or top grid)...

     

    The second half of the app is even uglier, it is in Window2.xaml  but it is a common scenario...

    image

    This does drag & drop across listboxes..
    it uses Data, but provides a Visual Adorner for feedback...

    The things to notice is that it implements the IDataDropObjectProvider interface ... so you can see how that works..

    You can click one of the planets and drag into Word or other external app that allows for text drops to get the XML representation of the planet..

    The listbox that you are dropping into does not allow duplicates.. so don't try dropping same thing twice..  it will quietly smirk at you..

     

    OK that covers my D&D walk through ...  The source for the sample in part 3 is here..

    {let me know if you find bugs or issues }

  • 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

    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

    Drag &amp; drop in WPF part2 ...

    • 0 Comments

    Last part, we focused on the drag...   In this part we will focus on drop (which is relatively straight forward) ...  

    DropTargets must set  AllowDrop = true

    It all begins with a Visual Element setting its AllowDrop to true; this tells the D&D subsystem your control is a drop target  ...  You can do this in XAML or in code.. 

     

    DragEnter, DragOver, DragLeave

    I hardly ever subscribe to any of these events..  but let's walk through the niceties on each ..

    • DragEnter -- happens when cursor/mouse enters;  useful for things like "highlighting" the target by showing an adorner, or similar ...     You would think that when mouseEnter, you can query the dataobject and provide feedback on whether the drop would be allowed... Well, you can, but it is pretty useless. Dragover can override that, so the place to handle the feedback is on DragOver..
    • DragLeave -- happens when cursor leaves..  Here you should undo any thing you did on DragEnter ..
    • Dragover -- here you can provide feedback to whether the drop would succeed .. 

    Walking through the Drop code with an example:

    From our previous work in progress project, I replaced old DropTarget StackPanel with a DropTargetText and DropTargetImages ...  
    DropTargetImages will handle DragOver,DragEnter, Dragleave  to check if dataObject type is Images and if not, it will set e.Effects to None, and provide some ugly feedback..
    DropTargetText will handle Dragover  and Drop  (since our old dataobject type is Text)

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

             // DROP
             this.DropTargetImages.DragEnter += new DragEventHandler(DropTargetImages_DragEnter);
             this.DropTargetImages.DragLeave += new DragEventHandler(DropTargetImages_DragLeave);
             this.DropTargetImages.DragOver += new DragEventHandler(DropTargetImages_DragOver);
             this.DropTargetText.DragOver += new DragEventHandler(DropTargetText_DragOver);
             this.DropTargetText.Drop += new DragEventHandler(DropTargetText_Drop);

         }

         void DropTargetText_Drop(object sender, DragEventArgs e)
         {
             IDataObject data = e.Data;

             if (data.GetDataPresent(DataFormats.Text))
             {
                 MessageBox.Show(
                     string.Format("right format, thanks for dropping '{0}'" ,
                     ((string)data.GetData(DataFormats.Text))));
             }

         }

         void DropTargetImages_DragOver(object sender, DragEventArgs e)
         {
             if (!e.Data.GetDataPresent("Images"))
             {
                 e.Effects = DragDropEffects.None;
                 e.Handled = true;
             }
         }

         void DropTargetText_DragOver(object sender, DragEventArgs e)
         {
             if (!e.Data.GetDataPresent("Text"))
             {

                 e.Effects = DragDropEffects.None;
                 e.Handled = true;
             }
         }

         void DropTargetImages_DragLeave(object sender, DragEventArgs e)
         {
             RunStoryboard("Timeline2");

         }

         void RunStoryboard(string name)
         {
             Storyboard sb = this.FindResource(name) as Storyboard ;
             System.Diagnostics.Debug.Assert(sb != null);
             sb.Begin(DropTargetImages);

         }

         void DropTargetImages_DragEnter(object sender, DragEventArgs e)
         {
             if (!e.Data.GetDataPresent("Image"))
             {
                 RunStoryboard("Timeline1");

                 e.Effects = DragDropEffects.None;
                 e.Handled = true;
             }

         }

     

    The source code for snippet above is here.

    Handling the Drop can be that straight forward,  but it almost never is that simple.. 

    In Part3, I can walk through much more samples of handling drop ... 

  • 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

    Eight WPF themes released…

    • 2 Comments

    Today, Rudi Grobler released a “WPF Themes Pack” on codeplex.
    The project includes eight XAML themes for WPF applications…

    Read his announcement here.   WPF Themes Codeplex project is here.

    I like his ‘Expression Dark’ theme.. It feels safe (and familiar) and I like the DavesGlossy theme too (screenshots below).

    Since some of the themes also ship with silverlight, this could be an easy way to create a consistent UI for an application that has a web client (Silverlight) and a destktop client (WPF).

    Thanks Rudi!!

    DavesGlossyControls ExpressionDark

  • Jaime Rodriguez

    Installing .NET Framework 3.5 in Windows 8 Consumer Preview

    • 4 Comments

     

    [I am traveling today, so it might be a one-off due to low band-width at the hotel, but in case it helps others]

    I was installing Live writer and got a prompt to install the .NET Framework 3.5, I agreed to do that and got a “following feature could not be installed. Error: 0x800F0906.

    Fortunately, I still had the ISO from when I installed consumer preview , so I used the local sources.
    If you see the error above, try this from an elevated command prompt.

    dism.exe /online /enable-feature /featurename:NetFX3 /Source:h:\sources\sxs
    where “h” is the drive where you have your win8 installation files (or where the iso is mounted, replace the path appropriately ).

     

    Happy Windows 8 coding!

  • Jaime Rodriguez

    built-in Styling and generic.xaml

    • 2 Comments

    Most people already know (from ScottGu’s blog post for example)  that in Silverlight 2 you can override the ControlTemplate for a Control and ‘re-define’ the look of the control.  However, I have received a few questions around the use of generic.xaml to accomplish this same task; I will try to share a few thoughts on this to tease you into digging deeper on your own.   If you are short on time, skip to [FAQs on built-in styles below]

    Some definitions on the recurrent “what is difference between style and template?” 

    Style is an object (in Markup or in Code) that sets properties of a control. 
    All that a style can do is set the value of existing properties on the control.  Imagine our control was a Car, a Style could say some thing like “wheelsize=17”, bodycolor=”cherry red”, “windowtreatment=”tinted”, etc.. 

    A template actually defines the pieces or parts of the car. For example, a template for a cheap convertible might not have a roof at all :)  or a template for a Car can decide if it is two doors or four doors, if it has four wheels or eight, etc. 
    When I explain it I always tell people, the template defines the skeleton, the Style dresses the pirate ( I like the pirate analogy cause some of them have one eye, or one leg, or one arm, etc. making good use of Templates).

    Where things get interesting is that a Style can set any property in the control, and Template is itself a property, so what you see the tools (like Blend) do most often is when you want to override the Template of a Control, they override the whole style and the Template property with it. 

    Stay with me… even if the above does not make sense, the rest of the article will help.

    Where generic.xaml comes in is in the “magic” that defines the default look for a control.  Let’s imagine we want to create a GelButton .. 

    public class GelButton : Button    
    {
    
    }

    Simple enough, now we want to use it in our Page.xaml user control, we add the namespace and the control.
    <UserControl x:Class="StylingSample.Page"
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Width="400" Height="300" 
        xmlns:samples="clr-namespace:StylingSample">
        <StackPanel Width="50" >
            <Button Content="Top"  Height="50"/> 
            <samples:GelButton Content="Cream" Height="50"></samples:GelButton>
            <Button Content="Bottom" Height="50"> </Button>
        </StackPanel>    
    </UserControl>

    Would you be surprised if the outcome looked like this?

    image

    I can’t tell you if you should be surprised or not (I am undecided myself), but I can tell you what happened!

    The control by default is lookless. You need to define the look for it. This is accomplished by assigning a valid ControlTemplate to the control  [via the Template property in the Control class].

    To assign the Template property, you could do some thing like:

    public GelButton ()
    {
    this
    .Template = XamlReader.Load ("<ControlTemplate xmlns='http://schemas.microsoft.com/client/2007'
    ><Grid ..> </Grid></ControlTemplate>");

    }

    but a better way to do it is to store the control template in a generic.xaml Resource Dictionary and then magically the run-time, will pick it up from there. Your template would be associated to your control via the TargetType attribute when defining the resource.  This template would now become what we call the “built-in style”.

    Here are the details on creating a built-in style.

    Generic.xaml is a ResourceDictionary –a property bag for resources – that you include in your assembly, at the root of the project.  If you are a WPFer you might be thinking it should be in themes\generic.xaml, I hear that is where it might end up, but for now (Silverlight 2 beta1), it needs to be in the root of the project.  The default (empty)  resource generic.xaml could look like this:

    <ResourceDictionary 
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    > 
    </ResourceDictionary>

    For defining the look & feel for our GelButton we need to start with some default template. Long term, this will be a right click inside Blend  (like in WPF); temporarily since Blend does not yet support styling,  I would recommend is using David Anson’s handy Stylebrowser application to copy the default Style for button and paste it into the resource dictionary.

    [unfortunately the default button template is too verbose, so for practical purposes here I am going to use a much simpler template].

    <ResourceDictionary 
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        xmlns:samples="clr-namespace:StylingSample;assembly=StylingSample"
    >
        <Style TargetType="samples:GelButton">
            <Setter Property="Background" Value="Black" />
            <Setter Property="Foreground" Value="White" />
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate  TargetType="samples:GelButton">
                        <Grid x:Name="RootElement" >
                            <Ellipse Width="{TemplateBinding Width}" 
                                Height="{TemplateBinding Height}" 
                                Fill="{TemplateBinding Background}"
                            /> 
                            <ContentPresenter 
                            Content="{TemplateBinding Content}" 
                            ContentTemplate="{TemplateBinding ContentTemplate}" 
                            Foreground="{TemplateBinding Foreground}" 
                            VerticalAlignment="Center" HorizontalAlignment="Center"
                            />
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </ResourceDictionary>

    Let’s dissect the work needed to create this template:

    1. Added a xmlns:samples to the resource dictionary.
      Notice the slightly different syntax from the namespaces you add to say a UserControl; in generic.xaml I included the assembly (which is the name of my DLL).
      If I had tried xmlns:samples="clr-namespace:StylingSample; with out the assembly=”StylingSample” it would not work. [trust me, I make that mistake often].
    2. Next I defined the style and I told it the TargetType I wanted this Style to be applied to; you usually do this when defining a Style so your template can do discovery of the properties and validate these, but when doing this in generic.xaml, the magic that happens on built-in templates uses this information to create a relationship (or I would say bind) between this Style and the type; now when ever the type is instantiated, if no other style is applied, this style will be used as the default style.
    3. The rest is simple styling stuff.  TemplateBinding is probably the most interesting part, this creates a binding between the property we are setting and the actual controls’ property. For example:  <ContentPresenter Foreground={TemplateBinding Foreground}" > creates a bind between the content presenter’s foreground and the Foreground in the actual control.  This will our UI styleabe from within the tools. Inside Blend or in XAML you can declare a button <GelButton Foreground=”Red” > or a <GelButton Foreground=White> and get flexibility as the template will get the value carried through. F
      For more info on all of this you should watch Karen Corby’s MIX presentation on “Rich,Dynamic UIs” ..

    Now, I can run the same code, with changing nothing other than the Resource dictionary I added and I get:

    image

    Since I did create a Templatebinding for background/Foreground , I can even have some fun..  After all, I promised some “meat”.. Need food!! sorry about that it is 1:30 PM ..

    <UserControl x:Class="StylingSample.Page"
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Width="400" Height="300" 
        xmlns:samples="clr-namespace:StylingSample">
        <StackPanel Width="50" Margin="0,20,0,0">
            <samples:GelButton Content="" Height="20.4" 
                    RenderTransformOrigin="0.5,0.5" Width="48.8" Canvas.ZIndex="2">
                <samples:GelButton.Background>
                    <RadialGradientBrush>
                        <GradientStop Color="#FFF5DEB3"/>
                        <GradientStop Color="#FFE0B05C" Offset="0.826"/>
                    </RadialGradientBrush>
                </samples:GelButton.Background>
                
            </samples:GelButton> 
            <samples:GelButton Content="Ham" Height="16" Canvas.ZIndex="1">
                <samples:GelButton.Background>
                    <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                        <GradientStop Color="#FFD64141"/>
                        <GradientStop Color="#FFE23939" Offset="1"/>
                        <GradientStop Color="#FEDAB6B6" Offset="0.43299999833106995"/>
                    </LinearGradientBrush>
                </samples:GelButton.Background>
                </samples:GelButton>
            <samples:GelButton Content="" Height="16"  >
                <samples:GelButton.Background>
                    <RadialGradientBrush>
                        <GradientStop Color="#FFF5DEB3"/>
                        <GradientStop Color="#FFECC06E" Offset="0.991"/>
                    </RadialGradientBrush>
                </samples:GelButton.Background>
            </samples:GelButton>
        </StackPanel>    
    </UserControl>
    image

    So, I just wasted 10 mins of your time and 40 of mine introducing you to generic.xaml and built-in styles.  I had promised to answer a few questions, here they are:

    [REF: FAQs on built-in styles]

    What are the benefits of built-in styles, why use generic.xaml instead of hardcoding the template?

    It is nice to store all your templates in a resource dictionary that you can easily swap – as opposed to having to do it in code-.   Imagine you needed to create three themes for your app, doing it with hardcoded templates would be hard.
    Also, If you put your Template in the ResourceDictionary the template can now reference other resources in the dictionary itself.

    Why is it that all examples I have seen are not using built-in styles?  we are always told to apply the style inline from App.xaml

    Built-in styles are designed for control authors, when you write a control, you provide look & feel.  If you look at example above, I had to inherited from Button class.  Most samples are purely styling a button, so they take a different approach.

    In the financials demonstrator, you inherited from Button and did nothing other than provide the built-in style, is this a best practice?

    I liked that approach (but I come from an enterprise background where we create bloated frameworks that often inherit just to create an abstraction in case some thing changes later) ; the one benefit you get is you can use your button any where with out having to explicitly refer to a style. 
    The disadvantage of course is that inheriting takes a bit of extra performance and memory; but this is pretty negligible from what I have seen. 

    Again, I don’t call it a best practice, more of a personal preference for me.  If you look at financials demonstrator now, I ended up adding a property later UseReflection, so now the button does have a reason to be its own class.

    Built-in styles sounds like I can change it all in one place? I don’t want to crowd my code with <Button Style=”{StaticResource GelButtonStyle}” >.

    That is right, if you can afford inheriting and the classes are not sealed.  That said, after building a few solutions I realized I had a false sense of centralization  [yes I made up the term]. 
    The argument is

    1) With built-in styles, I can change the style in one place. It is the same using App.xaml you change the style itself in one place for all.  What you are replicating a lot is the name of the style, but the style itself is in one place.

    When can I not use built-in styles? 
    if the class was sealed or they had protected the Template property then you would not be able to use this.

    Can I just create a generic.xaml and override the System.Windows.Controls templates with out inheriting?
    Not that I know of. It does not sound like a good idea; I tried it just to see if it worked and it did not work for me.
     

    Is applying a built-in style going to break or affect my state and parts?
    No. As long as your style uses the same names, the code will still pick all that as if it was an inline Style.

    We would not need built-in styles if you allowed TargetType every where, including on regular dictionaries, like WPF does.
    Fair point, these features are all being considered for later versions after 2.0 stay tuned, right now this way works, it is flexible and comprehensive.

    Will this approach work with Blend? Will I still be able to style in Blend.

    Yes!  Blend works with this already; that is how it picks the look & feel for System.WIndows.Controls today.

    Why do styles & Control templates always go together? Can I just do my Control template?
    My personal opinion is that if you need a template and not the style your template might be too stringent or too hard coded; it would be the equivalent of writing a ControlTemplate that does not have any TemplateBinding on it; don’t get me wrong, I am not saying this is wrong, I am just saying 99% of the time, this does not happen. With regards to simply providing the ControlTemplate in generic.xaml,  I don’t believe that would work.

    In the financials demonstrator, you named your class Button, for some thing that inherited from Sysetm.Windows.Controls.Button.. Is using the same name required?

    Absolutely not. I chose the name because I was going to override all controls, but I ended up changing my mind and that made it more confusing. Sorry about that; the name does not matter (as long as it does not conflict); from experience calling it Button will confuse you, don’t do that.

    If I use the built-in style, does that mean a ‘consumer’ of my button will not be able to style the button later?

    No! A consumer will still be able to style the button later and as well as override your template.

    --

    OK, I need to go eat.  This at least answers the questions I had; will try to come back to this at a different time.

  • Jaime Rodriguez

    Great WPF applications: Lawson’s Smart Office

    • 4 Comments

    Earlier in the year, you might have seen screen shots of Lawson’s Smart Office application.  Now, thanks to Adam Kinney and Matt Allbee (from Lawson), you can actually see it live in this 9 min interview/walk through!
    I recommend the high quality video for the interactivity, but for those not patient enough, I have sprinkled lots of screen shots below with a brief (not all-inclusive) summary around the usage of WPF.

    Smart Office is a front-end to Lawson's suite of applications -which includes Enterprise Resource Planning (ERP), Supply Chain Management (SCM), Enterprise Performance Management (EPM), and Custome Resource Management (CRM).
    The best description I have heard for Smart Office is Matt calling it an "information workspace".  A typical user can spend all day inside the app, as such it is a full-screen applications that simulates a shell -they call it a "Canvas"-. It has everything inside the app, window management, quick task switching, sidebar, drag-drop, shell and office integration, and of course lots of screens or apps to interact with the Lawson back-end. Smart Office aims to make an organization's people more effective, their processes more efficient, and the end-users happier. Smart Office focuses on the user's needs, and focuses on simplicity and meaningful collaboration to improve user and group productivity. 

    Here is how they 'get it done':

    An impeccable, comprehensive and incredibly effective user interface.
    Welcome

    Smart Office has it all:
    3D for navigation and for charting.

    Floating,translucent windows that blend well and allow users to take quick actions with out concealing the other user interface.

    Video for user tutorials and walk-throughs.

    Flow Documents are used for help files, to create an adaptive, high-quality reading experience.

    Uses VisualBrush(es) to implement previews in their taskbar -similar to Windows Vista's Alt-Tab-. It reuses this technique for data entry to preview forms before you open them. . 

     

    Figure 1. You can see the video player (bottom left) showing a recording of the application with a different theme.  Notice the rich windowing behavior and the translucency on the widgets and the widget library itself.  I could not fit all features in a window, click these links for missing features: 3D navigation, XPS help files, Visual Brushes in taskbar, detailed 3d chart.

     

    Personalization Nirvana!!
    DataEntryValidation

    Smart Office goes far beyond basic application skinning (where you can select the theme/color for your app).

    Smart Office allows you to create custom styles to effectively visualize your data; end-users can create client-side presentation rules with out writing any code!

    Rich windowing behavior; 100% customizable and adaptive.

    Most of the screens use “smart, dynamic layouts”. The user determines the size and position of a screen,  which of course the application automatically remembers; beyond positioning and sizing, the window dynamically scales its content to take advantage of the real-estate available and if you do not like it, no problem, you can manually control the scale of each screen.

    Figure 2. The pink field is a custom ‘trigger’ created via client-side wizard.
    Notice the windows all have a zoom icon to apply a scale to the window’s content. If content scales to larger than available space, scrollbars will appear. More screenshots: Styling wizardZoom/Scale Windowing, theming.



    Office and Shell integration as a productivity booster
    Excel02
    The application integrates with the OS allowing operations like drag & drop.

    It also integrates with Office allowing to send data from Smart Office to Outlook, Word, and Groove.

    Excel integration allows for seamless exporting and editing the data within Excel and immediately synchronizing it with the application; edits in Excel run through Lawson’s security and validation layer before being stored. 
    Figure 3. Excel integration. Imagine you need to edit 10 rows of data, maybe apply a formula; these tasks are easier to do in Excel.
    To see Outlook integration, click here.


    Overall, the application is brilliant. Huge kudos to the teams involved in creating this amazing productivity suite.  
    I am looking forward to other companies –including Microsoft- creating more applications like this one..

    Geeking it out (with non visual details):
    The application is a great S+S showcase. The client is a Full-Trust Click Once deployment with autoupdate functionality. 
    The back-end is a Java Service Layer talking to all kinds of code (.NET, Java, main-frame, you name it).
    The Office Add-ins are installed on demand or based on user preferences.
    The application is huge! Over 8000 screens and growing.  This is of course meta-data driven but  as you can tell from the stunning visuals they do a great job at leveraging styles, data and control templates, to create a great UX.   
    Will try to get the technical folks next time they are in Redmond to share all the lower-level details!

    More write-ups and showcases coming
    There are lots of other great WPF applications out there; but little time do record every one… I am hoping to showcase at least one video per month.  If you want to be in the list for showcases drop me an email, we always love to see how others use the technology.

    ttfn. Huge thanks to Matt for stopping by to record this and to Adam for doing the interview!.

  • 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

    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

    Running a Windows Phone Application under the lock screen

    • 0 Comments


    Last week, Microsoft published new Application content policies for Windows Phone 7 Apps.  One of the changes that got attention was the ability to run applications under a locked screen.  Refer to section 6.3 of the application certification requirements.

    In reality, the change is pretty minor, the capability to run under lock screen has been there since RTM.  Microsoft simply published some new ‘guidance’ on the expectations for applications that run under a locked screen.  That said, the change was interpreted as Microsoft relaxing the guidelines, since now it is clear that running under locked screen is not going to be frowned upon as long as you meet the requirements.  

    Why run under a locked screen? What are the benefits?
    Many applications are expected to run under a locked screen:  music players, run-tracking applications,  map applications, etc.  These applications will likely not change, as the certification guidelines had already approved these scenarios.  

    The changes to the certification requirements are aimed at apps that want to avoid tombstoning. For applications that are slow to recover from tombstoning ( maybe they make network calls and download a lot of data at start-up), they now can avoid the overhead of recovering from tombstoning when the application goes under locked screen.  You simply let the screen lock come on, and stay running quietly without consuming a lot of battery, and when the lock screen goes away, then you restore doing your business, but the gain to the end-user was they did not wait for a second or more while your app re-hydrated from a tombstone.   Since your app was never killed, response time is immediate. 

    Before we go much further on this topic, I do want to emphasize that running under lock is not an excuse so developers do not implement a super-fast, comprehensive tombstoning strategy. There are plenty of valid reasons why an app tombstones ( Choosers, App switching, etc.) that can’t be avoided, so you should still work hard to get the absolutely best tombstoning you can implement.

    Still, running under locked screen is a very nice to have for some apps, so I did a bit of digging and below are my tips, lessons learned, and even sample code, you can reuse to get your app to run under lock.

    Running Under Locked Screen
    In short, to let your application run under locked screen,  you must:

    1. Set PhoneApplicationService.Current.ApplicationIdleDetectionMode = IdleDetectionMode.Disabled;
    2. Handle the  Application’s RootFrame  Obscured and Unobscured events.  So you know when the lockscreen comes on.
      Obscured will be called when your app is getting locked.  Here, you should do as much as you can to minimize CPU or battery consumption. Stop all animations, stop making network calls, stop listening to accelerometer, or location changes, etc.. 
      Unobscured of course gets called when screens us getting unlocked. This is when the user comes back, and you pick where they left-off before phone went under the lock.
    3. [Optional] To have a great user experience with user in control,  you should prompt the user so they opt-in into running under lock.
    4. [Optional] You should expose as part of your application configuration settings an option for the user to change their mind or simply want to disable it and conserve power.
    5. There was only one gotcha that I noticed. If your application sets ApplicationDetectionMode to IdleDetectionMode.Disabled,  it can not change that back to IdleDetecionMode.Enabled until application is either launched again (so a full restart) or deactivated and then reactivated.

    Getting all this to run was trivial, but required a few more lines of code than the four items above (to save settings, fire events, etc.) .   I wrote this helper class called ApplicationIdleHelper.

    The class is a Singleton.  That is why the static Current property, so it can be shared throughout the app. The class supports change notification so you can bind to it. image

    • The class tracks three settings/properties:
      • RunsUnderLock is true if the user has elected to run under lock.  This setting is persisted across activations and launches of the app so we remember the user’s preference.  The property also supports change notification.
    • HasUserAgreedToRunUnderLock is true if the user was prompted using UI with a disclaimer that running under lock can consume battery.  This setting is serialized to ApplicationSetings, so that you don’t have to prompt the user every time.
    • IsRestartRequired is a transient property that does not get persisted. It simply flags whether the user has changed from IdleDetectionMode.Disabled to IdleDetectionMode.Enabled.  If we try set this transition, we will get an exception that tells us once Disabled is selected you can’t go back to Enabled.  Reality is that you can do it next time application is activated, so you just have to prompt the user to restart the app if they want the change to take place.
    • IsRunningUnderLock is a transient read-only property that supports change notification. This property tells you if the app is currently under a locked screen.  Good flag to make sure you are not doing work you are not supposed to, remember you are stealth under the lock screen (to preserve battery).
    • The application also supports three events:
      Locked  is fired when the Locked screen comes on. It is an abstraction so you don’t have to listen to RootFrame.Obscured event.
    • UnLocked fires when the application transitions from being in Locked state to it being in unlocked state and visible to the user. Again, equivalent to Unobscured event. 
       RestartRequired so that your classes are notified if the user has made a setting change that requires a restart (mainly going from IdleDetecionMode.Disabled to IdleDetecionMode.Enabled).

    That is all there is to it.  Plus, a bit of glue and a sample app for you to see it in action.

    About the sample app.

    WP7_1 (60)

    It is a very simple pivot app that plays music using MediaElement.
    You can toggle your preference to run under lock (or not to do that) by clicking the “Toggle current preference” button.
    You can play/stop music by pressing the Play/Stop button.
    The app will manage the settings for you. The UI under “Current settings” is data bounds to the ApplicationIdeleModeHelper so you can see it working.
    The events are fired properly too.

    Here are the behaviors you should see:

    If you are in the greedy pivot, and have chosen to  Run under Lock,  the music will continue playing under lock.
    If you are in the greedy pivot, but have not chosen to run under lock, the app will tombstone when the phone locks and there for the music will stop.
    If you are in the mellow pivot,  even if you have chosen to Run Under Lock,  the music will stop.  The application is not tombstoned, but it stoppped doing the work. It listens to the Locked event and stops doing work then, and then listens to Unlocked event and resumes there.  Again, without tombstoning. I just wanted to show you how you can “decrease” the work you do under lock, to avoid consuming battery.

    NOTE: To exercise the app you will need a device, as I don’t think the emulator runs under lock or simulates lock.  Also, when running this, make sure your device is not attached to Zune, as that will lock media library and prevent the app from playing music (and this sample does not throw error for that).

    The source for the app is on my Sky drive.  Let me know if you find issues or have questions.

    As my uncle used to say, “with great power comes great responsibility” ..  Running under lock is pretty neat, but advanced feature, do not abuse it.   Only use it for the kinds of apps that end-users would want to keep open even while locked.

    Happy Windows Phone coding!

  • Jaime Rodriguez

    A carousel control in Silverlight 2...

    • 2 Comments

    Check it out here...  Source is here!

    carousel


    It is very little code so I did not do a write-up. The code does have comments.
    Ideas? or  Improvements I considered but did not implement (yet )...

    • I debated using the animation system; generate ( n storyboards, where n = number of items ).. You can generate an animation that simulates points in a motion path..   this should smooth it out alot .. for the tiny sample I needed it felt like overhead; also I wanted to use DispatchTimer (the manual animation) as a reference.
    • It assumes that items in the panel don't have a transform applied..  [easy to check and multiply transform if needed]

    Credits:  Every thing I know about carousel came from a Lee brimelow's flash carousel tutorial.   

    Bugs/feedback, leave comments or email.

  • Jaime Rodriguez

    RenderTargetBitmap tips

    • 2 Comments

    Quite often,  I run into the “RenderTargeBitmap gave me an empty image” or “RenderTargetBitmap did not render my visual”..  
    Hint: this happens a lot to people putting stuff into StackPanels.

    The problem is that renderTargetBitmap is working at the Visual layer to render the visual and to do the rendering, it looks at the local properties of the visual (as expected).
    Layout happens at a different (higher) layer in the platform but often ‘parents’ to your visual will apply offsets or transforms to your visual to position it, since the parent is setting these properties on your actual visual, these properties will be picked up by RenderTargetBitmap logic.

    The best (and simplest) workaround I have seen came from Adam Smith ( WPF team lead and reliable WPF know it all). It simply wraps the Visual into a VisualBrush that it then draws into a DrawingContext.

    private static BitmapSource CaptureScreen(Visual target, double dpiX, double dpiY)
    {
        if (target == null)
        {
            return null;
        }
        Rect bounds = VisualTreeHelper.GetDescendantBounds(target);
        RenderTargetBitmap rtb = new RenderTargetBitmap((int)(bounds.Width * dpiX / 96.0),
                                                        (int)(bounds.Height * dpiY / 96.0),
                                                        dpiX,
                                                        dpiY,
                                                        PixelFormats.Pbgra32);
        DrawingVisual dv = new DrawingVisual();
        using (DrawingContext ctx = dv.RenderOpen())
        {
            VisualBrush vb = new VisualBrush(target);
            ctx.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
        }
        rtb.Render(dv);
        return rtb;
    }

    I like that workaround the best, and that is the one I use most of the time. 
    I know other people who workaround this same issue by wrapping the visual they are going to RTB in a Border. This abstracts any transforms that were being applied by the original parent, and now RTB works well.  I obviously don’t like this one as much since it ‘imposes’ on my Visual Trees.  Still, it works, so it is a choice.
     

    Here is a screenshot of a sample demonstrating the issue and the solutions:

    image
     

    The first column is the original images and buttons. They are all in a StackPanel.  The top 3 items in this panels are not wrapped inside a border, the latter 3 are wrapped in a border.

     

    The second column, shows what you would get if you just use RenderTargetBitmap.  Notice that the button and second image are missing; the reason is cause the StackPanel is offsetting them and RTB is picking that up.   
    Do notice that even with no special workaround, the ones wrapped in the border worked OK.

     

    The third column shows all the items rendered with the workaround above. Note that it works for all items, including the ones that had border.

     

    The source code for this small sample (including the workaround) is here.

    While I am into RenderTargetBitmap, a reminder to look at this KB article (and or QFE).
    In 3.5 SP1, we fixed a few leaks for RenderTargetBitmap, but there is still one open; it has to do with doing RenderTargetBitmap of a 3D scene that has VisualBrushes inside the scene.

  • Jaime Rodriguez

    DataTemplates in WPF..

    • 2 Comments

    Today, I mentioned "DataTemplates" to a new team that is just getting started w/ WPF..  

    templates came into the conversation because they were asking about time estimation for developing a few screens; being a developer, I argued that the binding and basic layout was trivial and we mostly should be be concerned with designer's time to decorate the screens (because they had conceptual);  of course this landed in MO ( the "show me!"  state) ... and I dumbly said "surely there is lots of samples on data templates out there" ...

    Sadly, I was 1/2 way right..  There are lots of samples that use DataTemplates but none that explain it .. www.beacosta.com is by my favorite WPF data blog and she does have samples on the more advanced DataTemplate concepts ( e.g. DataTemplateSelector) ... but no intro..

    So, here is my attempt to get out of the hole I digged this morning .. with out reinventing the wheel...

    Straight from the "DataTemplates' overview on MSDN" ...

    Data templates are a natural extension of styling controls and elements provided in Windows Presentation Foundation. Just as you can apply a visual Style to a user interface (UI) element, you can apply a DataTemplate that can determine both visual aspects of how the data is presented and how data binding accesses the presented data. By applying different data templates to the same data, you can flexibly change the visual appearance of the data in your application. Data templates can be applied to both Windows Presentation Foundation content controls (for example: Button and Hyperlink) and items controls (for example: ListBox, ComboBox, and Menu). These controls have built-in support for applying such data templates.

    Next, from Bea's blog,we steal some assets...  and plug in the article you should read after thiss one .. http://www.beacosta.com/Archive/2005_11_01_bcosta_archive.html

    Here she has a master-details sample, which I think is perfect to explain data templates:

    We have a planet DataObject, and we use templates to represent it in two different ways: simple text in a listbox ( master ) and detailed view for the selected item in the listbox.  Two templates, same data item, two totally different views ...   It is that simple, I am going to bed.  Ok, here I will show you ..

    Downloaded beas' sample... but

    • it is PDC build,
    • used an inline XMLData provider -great for her sample, but I extracted it to prove there is no magic tricks in there, and most importantly
    • I wanted to do it using EID ...

    So I decided to only reuse the assets (images + xml data source) ...  

    Opened her project and extracted the solarSystemPlants XMLData Provider she had ... onto its own file ...  ( planets.xml in attachment) ...

    {Setup}

    1. Open EID -
    2. Create New project -
    3. Save Project As    {optional, but I like to do it}
    4. Project - >  Add Existing Item
    5. navigate to her sample and select all the images
    6. Project - > Add Exiting Item
    7. Navigage to our planets.xml file

    { The (drag & drop) work }

    1. On the Data Palette
      1. Add new Xml Data Source
      2. Click Browse
      3. Select planets.xml 
      4. Click OK
    2. Still on Data Palette
      1. Expand the tree, to see SolarSystemPlants and Planet[n]
      2. Move your mouse over the Planet[n] node
      3. While mouseOver on Planet[n] left click to drag & drop the planet[n]  into scene1.
      4. "Select a Control to represent this data field" should come up ...
      5. Select ListBox
      6. Under Create Data binding, Click OK this will create a oneway binding good enough for this ...
      7. Under Create Data Template,  Uncheck  Planet (which should uncheck all)
      8. Now Check @Name    [which again does tha planet, annoys me a little but we will live with it for now]
      9. Under the Name, enter "PlanetNameOnlyTemplate"
      10. Click OK
    3. In Scene1, now we have a listox item with each planet name populated..  this is the simplest view we can have ( winforms even creates that) ... stick around, we will improve on that ...
    4. From Library, drag a  Content Control into our Scene 1  ( make it relatively big, this is our detailss view )
    5. Right Click on the COntent control, select "Bind To Data"
    6. from Data Sources, select SolarSystemDS
    7. Under fields, expand SolarSystemsDS to Planet [n] and select it
    8. Click Define Data Template
    9. Notice the same screen we saw earlier for Creating a Data Template comes up. 
      1. This time we leave everything checked
      2. For Image property (in planet) , we change it from TextBlock to Image ...
      3. Click OK

    [We now have two Data Templates for the Planet XML element; one is a simple, name only view, and the other is the Detailed View]....

    in Step 7 above we told the data binding code that we were binding to a Planet; this is right, but we specifically want to bind to the planet selected in the listbox, so lets:

    1. Right Click on Content Control again
    2. Select Bind To Data
    3. Click Element Property
    4. Select the ListBox
    5. Under properties, find Selected Item...   and select it
    6. Click Finish
    7. Run it ...    Project -> Test Project ..

    he , i bet you are thinking I messed up  :)  cause all u see is the listbox, now Select a planet from the listbox ...  voila ...

    Zero lines of code... and we are wired... master details .. see how the datatemplates helped to create two different UI representations for planet   { yes, it is ugly, we still need the designer} 

    One last thing is still bothering me .. The listbox still looks simple... Let's spice it up... what we can do is reuse the details template inside the lisbox.... 

    1. In Scene1, Click XAML Code  [
      1. we have to do this to quickly get around what I think is an EID bug or an I/O error with me.. I need to check on later EID build]
    2. Find the line that says

                    <DataTemplate x:Key="PlanetTemplate">

    and change it into:

                   <DataTemplate DataType="Planet" >

    By doing this we are now telling the Data template that it can be used any where a template is needed for type Planet  (and a Key has not been specified)

    {sorry next should be #3, but this editor does not have 'continue numbering]

    1. Go back to Design view 

    2. Double Click on the ListBox

    3. In properties palette,

      1. select  "ItemTemplate"

      2. Right click, and select "Clear/Default" -- so we clear it from using the old "PlanetNameOnlyTemplate" ... we are not going to set it to any thing but WPF magically will know it is a Planet XML element and find the template for it ..

      3. Now test it, "Project Test Project" ...

      4. You should get a "Detailed view inside the Listbox" ..pretty neat, similar to how Healthcare demo shows the Patients in the listbox...   those are styled our little sample is not ..

    That is it ... You have now seen the power of templates..  No lines of code,  under 15 mins, a nice detailed view inside a listbox ....  The designer can now decorate the template nicely.. and be done with it..

    The project with the solution is attached...  [create in July CTP under Windows Vista]

    You should now go back to www.beacosta.com and read about DataTemplateSelector and some deeper datatemplate topics ... 

    [ I will be back later this week w/ more advanced/ interesting posts, this one at least brought me back into the blog]

     

     

     

     

     

     

     

     

     

     

     

     

Page 1 of 9 (222 items) 12345»