Applications = Code + Markup : The WPF Parachute

I once commented (on a post by Karsten Januszewski where he’s talking about ‘Hitting the Curve’) that:

There isn’t a curve.  It’s more like jumping of a cliff, hitting the ground hard, dusting yourself off and then finding another cliff to do the same over.

Well, having just finished Petzold’s latest book (Microsoft Press, Amazon, Waterstones) I’m extending that analogy.  His book is your parachute;  having read it, you’ll find you have a better view of the WPF landscape, and when you reach the limit of your knowledge, you won’t hit the ground quite so hard. 

I wish I’d had this huge asset a year ago, and had had the time to fully absorb what is in it, instead of ploughing in head first with only samples, forum’s, blogs and a huge amount of trial, error and intuition. 

I envy those developers that are coming to the RTM version fresh, this technology has been proven in live environments, there is now a huge amount of documentation, samples and support and now with this book, there is everything a developer needs to create really stunning applications.

These are exciting times 😀

, , , ,

Advertisements

‘Petzold’s book’

Howard has leant me ‘Applications = Code + Markup’, which I’ve finally started to make headway on.  It’s really good, unlike most other IT publications you can read it like a novel, cover to cover.  It’s now been about 2 months since I did any hardcore WPF, so I’m a little rusty again, which has made reading this such a great refresher because I have no excuse for skipping bits, plus concepts I’d missed because of time, or misunderstood because of no clear explanation, I can now get straight in my head.

Like his other publications, this one assumes previous knowledge, but walks the user through the very basic of concepts first, I am especially intrigued about his decision to leave all XAML to the second part of the book, with the first part concentrating on the new concepts in WPF such as dependency properties and routed events.

I’ll give my final verdict when I’ve finished it, but so far I’m impressed.

What WPF is about, in 9 lines.

I came across this sample as an example of how powerful WPF is. 

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation&quot; xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml&quot; Title="DropShadow Inking Test">
    <Grid Background="Goldenrod">
        <Canvas Name="dropshadowBackplane" RenderTransform="1,0,0,1,3,3">
            <Canvas.Background>
                <VisualBrush Visual="{Binding ElementName=inkCanvas}" Opacity="0.50" />
            </Canvas.Background>
        </Canvas>
        <InkCanvas Name ="inkCanvas" Background ="Transparent" />
    </Grid >
</Window>

In 9 lines of XAML you get a canvas that you can ink on to with both a stylus and mouse and it has a drop shadow.

 

INotifyCollectionChanged with collections of collections

I’ve been struggling with consuming the INotifyCollectionChanged and INotifyPropertyChanged events in my code.  They were obviously doing their thing because the UI was updating to interaction changes.

It turned out to be a mix of not having Two-Way binding enabled and how I had set up my collection.

The data binding was hiding the issue I had with the collection, so thanks to Valentin Iliescu and Yves Dolce for helping me sort that out.

It would appear that you have to wrap your sub collections in a class, rather than just declaring an ObservableCollection<T> and adding the event handler directly.

 

So you have to do this

public class MyCollection : ObservableCollection<MyType>

{

  public MyCollection()

  {

    this.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);

  }

}

public class EnclosingType

{     

  private MyCollection myCollection;

  public EnclosingType ()
  {
    myCollection = new MyCollection ();
  }
}

 

Rather than doing this in your enclosing type

 

public class EnclosingType

{     

  private ObservableCollection< MyType>() myCollection;

  public EnclosingType ()
  {
    myCollection = new ObservableCollection< MyType>();
    myCollection.CollectionChanged += new NotifyCollectionChangedEventHandler(OnCollectionChanged);
  }
}

 

This doesn’t seem the correct behaviour and I’d appreciate anyone’s comment on it.

ReordereableListBox

This sample shows how to build a custom ListBox control where you can reorder the contents by dragging the items around.  I have based this on Marcelo’s drag/drop adorner but don’t actually make use of the DragDrop class.

 

 

First off we create the custom control class and add the needed event handlers

 

public class ReorderableListBox : ListBox
{
   protected override void OnInitialized(EventArgs e)
   {
      base.OnInitialized(e);

      this.PreviewMouseLeftButtonDown += new MouseButtonEventHandler(OnPreviewMouseDown);

      this.PreviewMouseLeftButtonUp += new MouseButtonEventHandler(OnPreviewMouseUp);

      this.SelectionChanged += new SelectionChangedEventHandler(OnSelectionChanged);

      this.PreviewMouseMove += newMouseEventHandler(OnPreviewMouseMove);

   }
}

 

The idea here is that we use the PreviewMouseLeftButtonDown and PreviewMouseLeftButtonUp events to decide when we are dragging and when we have dropped, the PreviewMouseMove event to move the adorner layer, and the SelectionChanged event to track the item that’s moving and the position we are going to move it to.

 

To manage the different states we need to declare the following

 

// The point at which to start drawing the adorner layer

Point dragStartPoint;

// Are we dragging?

bool dragging; 

// Have we selected the item we want to move?

bool dragItemSelected; 

// The AdornerLayer to draw the item we are dragging

AdornerLayer adornerLayer; 

// The Marcelo’s DropPreviewAdorner

DropPreviewAdorner overlayElement;

 

And the following public property and backing variable so we can access the original item from where where we are declared

 

private int originalItemIndex;

public int OriginalItemIndex
{
   get { return originalItemIndex; }
   set { originalItemIndex = value; }
}

 

This is all fairly simple logic but for completeness, when the PreviewMouseLeftButtonDown event is fired we set dragStartPoint and dragging.

 

void OnPreviewMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
   dragStartPoint = e.GetPosition(this); 
  
dragging = true;
}

 

This will be followed by the  SelectionChanged event being fired where we check for both dragging and !dragItemSelected.  If this is the case then we set dragItemSelected to true, and originalItemIndex to SelectedIndex.  Now that we know what item we want to move, we can set up the AdornerLayer.   Marcelo’s DragDropAdornerLayer takes the adornedElement and the adorningElement.  In this case the listbox itself and the item we want to move.  We create a new ContentPresenter and set the Content to be the SelectedItem and the ContentTemplate to be the ItemTemplate of the listbox, assigning it to overlayElement.  Finally we add the new DropPreviewAdorner to the AdornerLayer.

 

void OnSelectionChanged(object sender, SelectionChangedEventArgs e)
{
   if (dragging && !dragItemSelected)
   {
      this.dragItemSelected = true;
      this.originalItemIndex = this.SelectedIndex;

      ContentPresenter presenter = new ContentPresenter();

      presenter.Content = this.SelectedItem;
      presenter.ContentTemplate = this.ItemTemplate;

      this.overlayElement = new DropPreviewAdorner((UIElement)this, presenter);

      this.AdornerLayer.Add(overlayElement);
 
  }
}

 

The OnPreviewMouseMove event is quite simple too, we just set the LeftOffset and TopOffset for overlayElement based on the new mouseposition.

 

void ReorderableListBox_PreviewMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
   if (e.LeftButton == MouseButtonState.Pressed && dragging)
   {
      if (overlayElement != null)
      {
         Point currentPosition = (Point)e.GetPosition((IInputElement)this);
         overlayElement.LeftOffset = currentPosition.X;
         overlayElement.TopOffset = currentPosition.Y;
      }
   }       
}   

 

Finally we need to handle the reordering, since this will be dependent on if we are databound or not, we can defined a routedEvent called ItemsReorderedEvent thus leaving the reordering logic up to the controls host.

 

public static readonly RoutedEvent ItemsReorderedEvent;

public event RoutedEventHandler ItemsReordered
{
   add { base.AddHandler(ItemsReorderedEvent, value); }
   remove { base.RemoveHandler(ItemsReorderedEvent, value); }
}

 

static ReorderableListBox()
{
    ItemsReorderedEvent = EventManager.RegisterRoutedEvent("ItemsReordered",
    RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(ReorderableListBox));
}

 

We raise this event in the OnPreviewMouseLeftButtonUp event handler, after resetting dragging and dragItemSelected back to their original values, and removingthe adornerLayer.

 

void OnPreviewMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
   dragging = false;
   dragItemSelected = false;

   adornerLayer.Remove(overlayElement);

   RoutedEventArgs routedEventArgs;
   routedEventArgs = new RoutedEventArgs(ItemsReorderedEvent, this);

   base.RaiseEvent(routedEventArgs);
}

 

Since my listbox is databound, When we consume the ItemsReordered event, can cast the ItemsSource to an ObservableCollection of our bound type and use Move to do the reorder.

 

private void OnItemsReordered(object sernder, RoutedEventArgs e)
{
   ((ObservableCollection<CourseMark>)reorderableListBox.ItemsSource).Move(reorderableListBox.OriginalItemIndex, reorderableListBox.SelectedIndex);

}

Databound Master-Detail TabControl

I spent the weekend trying to get a master-detail tab control working, ie with a collection bound to the TabControls Itemsource to populate the TabItems and the TabItem Content containing the detail.

 

Since I couldn’t find anything on the web, I’ve put together a sample.

 

 

First off define your datasource, It’s the same data I use for the RegattaManager but in xml form.

 

     <XmlDataProvider x:Key="StartLineCollectionDS" XPath="/StartLineCollection">

      <x:XData>

        <StartLineCollection xmlns="">

          <RaceLine>

            <ShortName>RYS (White)</ShortName>

            <RaceCollection>

              <Race>

                <RaceName>Etchells</RaceName>

                <StartTime>10:25</StartTime>               

              </Race>

              <Race>

                <RaceName>RSK6 and 1720s</RaceName>

                <StartTime>10:35</StartTime>               

              </Race>

              <Race>

                <RaceName>Flying Fifteen</RaceName>

                <StartTime>10:45</StartTime>               

              </Race>

            </RaceCollection>

          </RaceLine>

          <RaceLine>

            <ShortName>RYS (Black)</ShortName>

            <RaceCollection>

              <Race>

                <RaceName>IRC 0</RaceName>

                <StartTime>10:30</StartTime>               

              </Race>

              <Race>

                <RaceName>IRC 1</RaceName>

                <StartTime>10:40</StartTime>               

              </Race>

              <Race>

                <RaceName>IRC 3</RaceName>

                <StartTime>10:50</StartTime>

              </Race>

            </RaceCollection>

          </RaceLine>

        </StartLineCollection>

      </x:XData>

    </XmlDataProvider>

 

Next define a grid for your TabControl to live in and set the Grid’s data context to be the data source

 

<Grid DataContext="{Binding Source={StaticResource StartLineCollectionDS}}"/>

 

Having defined the grid we can add the tab control and set it’s ItemsSource to bind to the RaceLine element.

 

<GridDataContext="{Binding Source={StaticResource StartLineCollectionDS}}">

    <Grid.ColumnDefinitions>

      <ColumnDefinition Width="*"/>

    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>

      <RowDefinition/>

    </Grid.RowDefinitions>

 

    <TabControl … ItemsSource="{Binding XPath=RaceLine}" />

</Grid>

 

Next up you need to add a DataTemplate for both the ItemsControl.ItemTemplate and the ItemsControl.ContentTemplate, the first sets up the databinding to populate the TabControl with TabItems

 

 

      <TabControl.ItemTemplate>

        <DataTemplate>

          <TextBlock Text="{Binding XPath=ShortName}" />

        </DataTemplate>

      </TabControl.ItemTemplate>

 

The second defines the content for each tab.

 

      <TabControl.ContentTemplate>

        <DataTemplate>

          <ListBox ItemsSource="{Binding XPath=RaceCollection/Race}” />

        </DataTemplate>

      </TabControl.ContentTemplate>

 

Here we have just added a list box with its ItemsSource pointing to the object you want to display.  We can add an ItemTemplate to display members from the Object.

 

      <TabControl.ContentTemplate>

       <DataTemplate>

         <ListBox ItemsSource="{Binding XPath=RaceCollection/Race}” ItemTemplate="{DynamicResource RaceCollectionTemplate}/>

       </DataTemplate>

      </TabControl.ContentTemplate>

 

And define it in the resources for the container…

 

  <Window.Resources>

    <DataTemplate x:Key="RaceCollectionTemplate">

      <Grid Margin="0,0,0,3">

        <Grid.ColumnDefinitions>

          <ColumnDefinition Width="0.740810692573908*"/>

          <ColumnDefinition Width="0.259189307426092*"/>

        </Grid.ColumnDefinitions>      

 

        <TextBlock Text="{Binding XPath=StartTime}"/>

        <TextBlock Text="{Binding XPath=RaceName}" />

      </Grid>     

    </DataTemplate>

  </Window.Resources>

 

You can quite easily extend this to show more detail from your data structure in a separate Control the same way you would normally do master-detail.

 

The entire code for this sample can be downloaded from http://www.quarrie.net/WPFSamples/01TabControl.zip.

 

 

Talking about Atomic Bloggraphy: Ignorance and Fear

Karl has hit the nail on the head with this post.

"WPF is a fantastic technology that has a huge amount to offer my users. It’s not about giving users pretty interfaces, it’s about giving users intuitive interfaces. WPF provides the most expressive environment for combining classic UI elements, 2D/3D graphics, videos and animation currently available. I want my users to have the best possible experience when using my software."

The human being is an incredibly tactile creature, and because of this, if we see something we like, we pick it up and touch it, and whether we are concious of it or not, this touch imbues a lot of understanding.  This haptic response is missing from the computer world, the tool we use the most in our daily lives, and I cannot think of another which provides so little touch feedback.

So for now that feedback has to be solely provided through user experience (UX).  Some people may say that glowing buttons, animation, gradient fills, curved dialogs and 3D as well as beeps, plinks and buzzes are just toys for the boys.  Whilst I am one of those boys I believe it goes deeper, and that the greater stimulus and feedback provided by things like Flash and AJAX as well as owner-drawn custom UIs make user interfaces in general alot more accessible.  WPF is going to bring that accessibility to the mainstream by making it easy to create levels of user feedback and interactivity that are just not possible within the time and experience limits imposed on most projects.

I am working on a project which epitomizes what I’ve just said.  I have computerised a system which was purely touch based and have had to design a UI with non computer users in mind, the first attempt was satisfactory, I am hoping I can make it much better this time.