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.

 

 

New style…

Let me now if it’s giving you a head ache….

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.

Atomic Bloggraphy on WPF

Well, now there is one more of us to add to the list of WPF developers.  I’ve set myself the unenviable challenge of convincing Mark, Daz and Jenny that .NET and WinFX are the way to go.  Since one is writing a distributed molecular modelling system using Fortran on a linux cluster, one has just submitted his PhD on stochastic diffusion searches and the other is maintaining legacy Delphi apps, I think I’ve got my work cut out.

Linz World

My wife Lindsey has started writing her blog again.  She suffers from a chronic condition called fibromyalgia, which she intends to write about I think.  It’s purdy and green and has awful photos of me on it, so go along have a laugh and give her a little support.

A different debugging paradigm

Using pre-release software is always a risky business, there isn’t much help, code examples are sparse and bugs are prevalent, but that is mostly made up for by the large community that’s embracing it, the openness and willingnesss of the product team to deal with developers and the hugely passionate ‘Evangelists’ who are pushing awareness of the technology.  I’ve experienced this with everything I’ve worked on over the last few years, MDX, .NET 2.0 etc.  I have no doubt that without this emergent support structure I wouldn’t have been able to get the first release of the Regatta Manager completed on time.
 
(Can you sense the but coming?)
 
But…WPF is different.  It’s different because so much is new, not just in technology but in process and concept.  So much can be defined declaratively, for example, that you run the risk (as I have) of getting into the same traps that web development used to, markup so complex that it’s too hard to manage.  XAMLs power is at the same time it’s (current) major weakness.  Work for too long in EID and you’ll go back to Visual Studio and not know where to start, but at the same time custom controls are so hard to work with because of resource loading issues that sometimes you wonder if it’s worth it.  Which brings on to the main topic off this post, debugging.  Specifically the lack of it in any conventional sense.
 
As XAML is decalarative you are solely reliant on schema support, design time value checking and the accuracy of any samples you use for help.  With the breaking changes between CTPs, the disparity between the XAML that EID produces and the schema VS uses to validate it and Cider not being able to render controls in clr-namespaces you’re not in for an easy time.
 
The software developer without his debugger is like a physician without his stethoscope, there are other ways to diagnose problems, but you feel slightly impotent without your first line of attack.  Things will obviously get better over time as platform and tool support is refined and improved, but until then be prepared to make extensive use of the other instruments in your bag.