Simon さんのプロフィールPsiSpaceフォトブログつながり ツール ヘルプ

ブログ


11月9日

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

, , , ,

10月2日

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

5月15日

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" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 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.

 

5月1日

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.

4月29日

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

}

4月24日

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.

 

<Grid ... DataContext="{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.

 

 

4月17日

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.

4月13日

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.

4月11日

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.
4月7日

Wrecking Ball

Karsten blogged the other day about the curve and I commented that it's more like cliff.
 
Well having achieved an incredible amount in the last week I hit the ground, and I have spent the last day and a half picking myself up.  I just found the problem.  I'd declared a path which had good 1000 points to it.  At the end of this line was a reference to a style that I'd deleted and neither Cider or EID was complaining about, but when the control was put on a page or the app run would throw this wonderfully enlightning exception
 
System.Windows.Markup.BamlParseException was unhandled
  Message="Error at element 'Canvas' in markup file 'MainWindow.xaml' : Exception has been thrown by the target of an invocation.."
  Source="PresentationFramework"
  LineNumber=0
  LinePosition=0
 
So having removed the offending reference I am off to find another cliff to climb and then jump off.
3月29日

WPF Controls

I've spent the last couple of nights struggling with WPF user/custom controls. 
 
Currently I am adding usercontrols to the children collection of a canvas in the MainWindow OnInitialized() override, getting the data to create the child from the database.
 
Now ideally this could all be done declaratively, by binding an observable collection to a custom control which can handle the user control creation and presentation, and encapsulating all that logic in the control rather than the main window.
 
I'll have another go tonight I think.
 
 
3月13日

Sparkly Avalon

I spent the weekend playing with the Windows Presentation Framework February CTP and the March CTP of Microsoft Expression Interactive Designer.  At the end of it I had the basics of a replacement for the CourseSetter, something which took me 3 months to write in MDX.  Bit annoying to have to throw away the engine I wrote, but not having to do the painting, data binding and dialog handling manually will seriously save me time, especially considering the new spec for this year.