Click here to Skip to main content
Email Password   helpLost your password?

Introduction

I recently started a new job where I am employed as a WPF developer. When I arrived the guys there gave me a brief that was to make a cool app, and they really liked the look and feel of the FamilyShow exemplar by Vertigo. Which I also love, that and Tangerine by Infragistics are my favourite WPF demos.

What I liked in both where the fluid movements and the diagraming approach used in the FamilyShow exemplar particularly. The guys where I just started working asked me how hard it would be to create something like the diagramming component seen in the FamilyShow exemplar. So without further ado I contacted my favourite partner in weird WPF breifs, Mr Fredrik Bornander, who I love working with on these stranger ideas. We seem to manage to do a reasonable job together, at least I think anyway.

This article will describe a tree like diagram component that we have nicknamed the "SpiderControl".

Here is a screenshot just to wet your appetite:



The rest of this article will describe how we went about building this little control

What Does It Do

The following is a list of what the control actually does

 

Hows It Made

So now onto the nitty gritty, which is the part you are probably wanting to read anyhow.

So first lets just have a quick look at the basic structure

Ill split this into 2 diagrams for no other reason than I couldnt figure out how to get words SmartArt to add more levels to its standard SmartArt diagrams, curse technology.

It can be seen from the above diagram that the HostWindow holds an instance of a DragViewer. So the windows code is simply the following:

<Window x:Class="SpiderTreeControl.HostWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:diagram="clr-namespace:SpiderTreeControl.Diagram"  
    WindowStartupLocation="CenterScreen"
    Title="HostWindow" Height="400" Width="400">
    <Grid>
        <diagram:DragViewer x:Name="dragViewer" 
                            Width="auto" Height="auto" 
                            Margin="0"/>
    </Grid>
</Window>

And then if we focus our attention to the actual DragViewer, where we wrap a DiagramViewer within a FrictionScrollViewer.

The FrictionScrollViewer is a specialized ScrollViewer that acts using friction to create nice fluid drag operations. I talk more about this on an older blog entry of mine, which you can read about using my blog entry http://sachabarber.net/?p=225

<UserControl x:Class="SpiderTreeControl.Diagram.DragViewer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:diagram="clr-namespace:SpiderTreeControl.Diagram;assembly="                
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="auto" Width="auto">


    <diagram:FrictionScrollViewer x:Name="sv" Style="{StaticResource ScrollViewerStyle}">
        <diagram:DiagramViewer x:Name="diagramViewer" Margin="0" Width="2000" Height="2000"/>
    </diagram:FrictionScrollViewer>
</UserControl>

The Style you see for the scrollbar is achieved using some Styles which are located within the AppStyles.xaml ResourceDictionary. This is what gives the ScrollViewer its appearance as shown below:

But all of that is simple eye candy, we need to get to nuts and bolts.

DragViewer class

So going back to the DragViewer, which holds an instance of the DiagramViewer. In code behind the DragViewer is responsible for setting up the nodes collection that is used for its embedded DiagramViewer.

This is done is code as follows:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;

namespace SpiderTreeControl.Diagram
{
    /// <summary>
    /// Interaction logic for DragViewer.xaml
    /// </summary>
    public partial class DragViewer : UserControl
    {

        public DragViewer()
        {
            InitializeComponent();
            this.Loaded+=delegate
            {
                LoadDiagramNodes();
            };
        }

        public void LoadDiagramNodes()
        {

            DiagramNode root = new DiagramNode("Root", null, "../Images/DiagramRootNode.png", "Dummy1View","this is the root node");

            DiagramNode a = new DiagramNode("A", root, "../Images/DiagramNode.png", "Dummy1View", "this is node A");
            DiagramNode b = new DiagramNode("B", root, "../Images/DiagramNode.png", "Dummy1View", "this is node B");
            DiagramNode c = new DiagramNode("C", root, "../Images/DiagramNode.png", "Dummy1View", "this is node C");
            DiagramNode d = new DiagramNode("D", root, "../Images/DiagramNode.png", "Dummy1View", "this is node D");
            DiagramNode e = new DiagramNode("E", root, "../Images/DiagramNode.png", "Dummy1View", "this is node E");
            DiagramNode f = new DiagramNode("F", root, "../Images/DiagramNode.png", "Dummy1View", "this is node F");

            diagramViewer.RootNode = root;
            diagramViewer.FrictionScrollViewer = this.sv;
        }
    }
}

Were individual DiagramNode objects are created and the relationship between them are establish by passing the relavant DiagramNode in as a constructor parameter to another DiagramNode. Obviously in the case of the root DiagramNode, this value is null. The last thing that is done is that the embedded DiagramViewer has its RootNode property set to the root DiagramNode. There is also a requirement to set the embedded DiagramViewers FrictionScrollViewer property, such that the DiagramViewers layout algorithm can react to new ScrollViewer positions should the user move the ScrollViewer or drag the diagram.

DiagramViewer class

Is where all the layout of contained DiagramNodes occurs. This is a simple user control that contains a single TreeCanvas that holds the actual collection of DiagramNodes, and draws the lines between them, which is discussed below. The DiagramViewer also listens to events from the DiagramNodes such as Selected/Collapsed/Expanded, where it will perform the layout based on the node selection.

The DiagramViewer uses a radial algorithm to lay out the collection of child nodes around a parent node. This is done using standard trigonometry maths. The basic idea is that each DiagramNode is given a bounding circle to ensure that all nodes have a uniform size, and then an angle between nodes is calculated.

 

This process is done on the NodeExpanded(), as shown below:

private void NodeExpanded(DiagramNode sender, RoutedEventArgs eventArguments)
{
    rootNode.Location = new Point(
        (double)GetValue(Canvas.ActualWidthProperty) / 2.0,
        (double)GetValue(Canvas.ActualHeightProperty) / 2.0);


    MakeChildrenVisible(sender);

    if (sender.DiagramParent != null)
    {
        sender.DiagramParent.Visibility = Visibility.Visible;
        foreach (DiagramNode sibling in sender.DiagramParent.DiagramChildren)
        {
            if (sibling != sender)
                sibling.Visibility = Visibility.Collapsed;
        }
        if (sender.DiagramParent.DiagramParent != null)
            sender.DiagramParent.DiagramParent.Visibility = Visibility.Collapsed;
    }

    if (sender.DiagramChildren.Count > 0)
    {
        double startAngle = CalculateStartAngle(sender);
        double angleBetweenChildren = (sender == rootNode ? Math.PI * 2.0 : Math.PI) / 
					((double)sender.DiagramChildren.Count - 0);

        double legDistance = CalculateLegDistance(sender, angleBetweenChildren);

        for (int i = 0; i < sender.DiagramChildren.Count; ++i)
        {
            DiagramNode child = sender.DiagramChildren[i];
            child.Selected += new NodeStateChangedHandler(NodeSelected);
            child.Expanded += new NodeStateChangedHandler(NodeExpanded);
            child.Collapsed += new NodeStateChangedHandler(NodeCollapsed);

            Point parentLocation = sender.Location;

            child.Location = new Point(
                parentLocation.X + Math.Cos(startAngle + angleBetweenChildren * (double)i) * legDistance,
                parentLocation.Y + Math.Sin(startAngle + angleBetweenChildren * (double)i) * legDistance);

            foreach (DiagramNode childsChild in child.DiagramChildren)
            {
                childsChild.Visibility = Visibility.Collapsed;
            }
        }
    }

    BaseCanvas.InvalidateArrange();
    BaseCanvas.UpdateLayout();
    BaseCanvas.InvalidateVisual();
}

The above process also relies on another process, which is the process which works out the leg distances (the length of the line to draw from one node to its child) between nodes.

This is as shown below:

private static double CalculateLegDistance(DiagramNode sender, double angleBetweenChildren)
{
    double legDistance = 1.0;
    double childToChildMinDistance = 1.0;
    foreach (DiagramNode child in sender.DiagramChildren)
    {
        legDistance = Math.Max(legDistance, 
			sender.BoundingCircle + child.BoundingCircle);
        foreach (DiagramNode otherChild in sender.DiagramChildren)
        {
            if (otherChild != child)
            {
                childToChildMinDistance = 
				Math.Max(childToChildMinDistance, 
				child.BoundingCircle + otherChild.BoundingCircle);
            }
        }
    }

    legDistance = Math.Max(
        legDistance,
        (childToChildMinDistance / 2.0) / Math.Sin(angleBetweenChildren / 2.0));
    return legDistance;
}

 

TreeCanvas class

This is a specialized Canvas control that simply draws the lines between all the DiagramNodes, currently shown within the DiagramViewer. This is done by overriding the OnRender() method of the Canvas control. This is shown below:

protected override void OnRender(System.Windows.Media.DrawingContext dc)
{
    base.OnRender(dc);
    foreach (UIElement uiElement in Children)
    {
        if (uiElement is DiagramNode)
        {
            DiagramNode node = (DiagramNode)uiElement;

            if (node.Visibility == Visibility.Visible)
            {
                if (node.DiagramParent != null && 
                    node.DiagramParent.Visibility == Visibility.Visible)
                {
                    dc.DrawLine(new Pen(Brushes.Black, 2.0), 
                        node.Location, node.DiagramParent.Location);
                }
            }
        }
    }
}

DiagramNode class

This is a pretty standard WPF UserControl that represents a single DiagramNode within the DiagramViewer. Its pretty standard stuff really a few buttons for the expanded/collapsed and selected states. Lets see the XAML for it shall we.

<UserControl x:Class="SpiderTreeControl.Diagram."
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:diagram="clr-namespace:SpiderTreeControl.Diagram"  
             
    Height="80" Width="90" 
    Background="Transparent"
    BorderBrush="Transparent">


    <UserControl.CommandBindings>
        <CommandBinding Command="{x:Static diagram:DiagramNode.expandCommand}" 
            CanExecute="ExpandCommand_CanExecute"
            Executed="ExpandCommand_Executed"/>

        <CommandBinding Command="{x:Static diagram:DiagramNode.collapseCommand}" 
            CanExecute="CollapseCommand_CanExecute"
            Executed="CollapseCommand_Executed"/>

    </UserControl.CommandBindings>



    <UserControl.Resources>

        <ControlTemplate x:Key="expandCollapseButton" TargetType="{x:Type Button}">
            <Grid Width="20" Height="20" Background="Transparent">
                <Ellipse Width="20" Height="20" Fill="DarkGray"/>
                <Ellipse Width="16" Height="16" Fill="WhiteSmoke"/>
                
                <Label x:Name="lbl" Content="{TemplateBinding Content}" 
                       HorizontalAlignment="Center" VerticalAlignment="Center"
                       Background="Transparent"
                       FontFamily="Arial Black" FontSize="10"/>
            </Grid>
            <ControlTemplate.Triggers>
                <Trigger Property="IsEnabled" Value="false">
                    <Setter TargetName="lbl" Property="Foreground" Value="DarkGray"/>
                </Trigger>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="BitmapEffect">
                        <Setter.Value>
                            <DropShadowBitmapEffect />
                        </Setter.Value>
                    </Setter>
                </Trigger>
            </ControlTemplate.Triggers>
        </ControlTemplate>

    </UserControl.Resources>
    
    
    <Canvas Background="Transparent" Width="90" Height="80" >
        <Label x:Name="NodeName" Content="Name" FontFamily="Arial Black" 
               FontSize="14" Canvas.ZIndex="1"/>
        
        <Button x:Name="btnNavigate" 
                Template="{StaticResource simpleImageButtonTemplate}"
                Click="btnNavigate_Click" Width="60" Height="60" 
                Canvas.Left="20" Canvas.Top="20"
                Canvas.ZIndex="0"/>
        
        <Button x:Name="ExpandButton" Content="+" 
                Canvas.Top="30" Canvas.Left="0"
                Template="{StaticResource expandCollapseButton}"
                Command="{x:Static diagram:DiagramNode.expandCommand}" />


        <Button x:Name="CollapseButton" Content="-" 
                Canvas.Top="55" Canvas.Left="0"
                Template="{StaticResource expandCollapseButton}"
                Command="{x:Static diagram:DiagramNode.collapseCommand}" />
    </Canvas>
</UserControl>

Each DiagramNode looks like the following:

 

Known Issues

There is only 1 known issue, but this is actually the case with pretty much every diagramming solution I have seen within WPF, including the FamilyShow exemplar by Vertigo, and lots of others which I had to evaluate in my last job. Basically in order to dynamically position diagram nodes, you have to use a container panel that supports X/Y positioning, which means using a Canvas.

Which is fine, but this means you must must ensure that the Canvas is big enough to accomodate the largest layout positions that you algorithm demands. Not a huge problem, but it is one that you need to be aware of.

To this end you will find that within the embedded DiagramViewer within the DragViewer control within the demo application, that the DiagramViewer has a fixed size of 2000 by 2000, which is declared as follows:

<UserControl x:Class="SpiderTreeControl.Diagram.DragViewer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:diagram="clr-namespace:SpiderTreeControl.Diagram;assembly="                
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Height="auto" Width="auto">


    <diagram:FrictionScrollViewer x:Name="sv" Style="{StaticResource ScrollViewerStyle}">
        <diagram:DiagramViewer x:Name="diagramViewer" Margin="0" Width="2000" Height="2000"/>
    </diagram:FrictionScrollViewer>
</UserControl>

Other than this single issue, which as I say you will probably see in practically all diagrmming components for WPF, there are no known issues.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralBrilliant... I was looking for something just like this!
Arshad_Ebrahim
6:16 20 Oct '09  
Excellent work... you guys are too clever Smile
Generalnice article
KunalChowdhury
23:49 3 Aug '09  
nice article... it gives a good thought to my app...

thanks ... Thumbs Up

Regards,
- Kunal Chowdhury (My Blog)


GeneralCreate Article and Code + question (problem)
Yiping Zou
14:32 8 Jul '09  
Thank you so much for sharing such fantastic SpiderTreeControl.
I am trying to convert the spiderTreeControl app to WpfControlLibrary and drag the control DiagViewer from toolbox to my window app form.

It works greate, but the images does not loaded somehow.
I am brand newbi for WPF. Is there anything I am missing? do I need to consider some issues while convert it to Dll?

THANKS,

Yiping Zou
GeneralCan you please post the source code which gets compiled in Visual Studio 2005
Harish Pulimi
4:57 13 May '09  
I have only Visual Studio 2005 and this project is not getting compiled in that.

Thanks in advance
Harish
GeneralRe: Can you please post the source code which gets compiled in Visual Studio 2005
Sacha Barber
5:46 13 May '09  
That would be cos I have not used VS2005 for quite some time. Dont even have it installed. You can copy the class code.

Sacha Barber
  • Microsoft Visual C# MVP 2008/2009
  • Codeproject MVP 2008/2009
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Can you please post the source code which gets compiled in Visual Studio 2005
Fredrik Bornander
12:25 17 May '09  
Hi Harish,

I'm guessing you've already set up a 2005 project, but if you haven't I've created one for you that you can download.

SpiderTreeControl.zip[^]

/Fredrik
GeneralRe: Can you please post the source code which gets compiled in Visual Studio 2005
Harish Pulimi
0:40 19 May '09  
Hi

Thanks for providing the file, but unfortunately this is also not opening in my Visual Studio 2005 SP1. Its giving the error "The project type is not supported by the installation", seems my installation needs to be modified.

Thanks again
Harish
GeneralGreat Article + a question
Andre Thomas
4:22 19 Feb '09  
This is really good stuff.

I have a question, and please ignore my 'newbieness' I'm still trying to get to grips with WPF. How could one create the nodes dynamically instead of hard coding, like say from an xml or db?

cheers

andre
GeneralRe: Great Article + a question
Sacha Barber
5:11 19 Feb '09  
The part where all the hard coded nodes are, simply use a XmlReader / DataSet or whatever to load the nodes.

You could use some XLINQ to do it, if using XML.

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

QuestionSome questions
Sk8tz
20:50 10 Jan '09  
Once again another great control, yet simple in design.

Just some questions,

1) A pop up window to display (to hold my context) menu when I hover over a node with some transparency, how can I do that with the RoutedUICommand commands. D'Oh!
2) Any idea how to implement a zoom functionality on the DiagramViewer. Basically I perform functionality where I zoom onto a node, and apply transforms to it. D'Oh!

Also Not sure if this is a bug, but to hear your thoughts on it

I use a DockPanel with LastChildFill="True" and the DiagramViewer being the last content UIElement, if the DiagramViewer now resizes, the ScrollToCenterTarget doesnt correctly align to the center of the new panel size.

Thanks again for a nice control Shucks Big Grin , as its forming the bases for my new app.

Sk8tz

Sk8tZ

AnswerRe: Some questions
Sacha Barber
0:44 11 Jan '09  
Sk8tz wrote:
Once again another great control, yet simple in design.

Thanks


Sk8tz wrote:
1) A pop up window to display (to hold my context) menu when I hover over a node with some transparency, how can I do that with the RoutedUICommand commands.

You would just create a property on the Node and I would then just create a tooltip style that is applied in XAML within the Nodes Style. The Tooltip could bind to properties on the node using Databinding, you may need to use some RelativeSource bindings to get to actual node from tooltip if you use a seperate style.


Sk8tz wrote:
2) Any idea how to implement a zoom functionality on the DiagramViewer. Basically I perform functionality where I zoom onto a node, and apply transforms to it.

This is fairly easy just create a ScaleTransform, there are loads of example of this on the net.


Sk8tz wrote:
I use a DockPanel with LastChildFill="True" and the DiagramViewer being the last content UIElement, if the DiagramViewer now resizes, the ScrollToCenterTarget doesnt correctly align to the center of the new panel size.

No thats not a bug, basically what happens with all the layout containers apart from Canvas is that they take as much space as they can, so children within these controls also get affected. And if you try and get the Size you may find that its set to Auto which is really Double.NAN which is not usable (as its not a valid double/int value, that can be used in math calcs) in thehe layout algorithm. This is why it has to be a canvas control with fixed sizes.


Hope this answers your queries.

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: Some questions
Sk8tz
19:25 11 Jan '09  
I'm still newist to wpf, but will the suggestions give it a shot. A last request, do you have a silverlight conversion of this control, any intentions of doing a conversion.

Also any updates to the article, looking forward to any new changes.

Thanks for your reply

Sk8tZ

GeneralRe: Some questions
Sacha Barber
22:52 11 Jan '09  
No Silverlight version and no conversion planned, but it wouldn't be that bad, if you wanted to try it, I'm sure you would find it ok.

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralVS 2008 SP1 Designer glitch
YAlexopoulos
6:31 1 Oct '08  
Great article, but when I try to open HostWindow Cider blows with error: Could not create an instance of type DragViewer.

Bizzare as this is a UserControl.Confused
GeneralRe: VS 2008 SP1 Designer glitch
Sacha Barber
7:10 1 Oct '08  
Most odd works for me.

Though there is not much to see for that class in the UI Designer.

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralRe: VS 2008 SP1 Designer glitch
YAlexopoulos
10:09 1 Oct '08  
Nevermind Sacha, I did try it on another machine with VS 2008 SP1, but got the same error message.

Thanks for replying Smile
GeneralRe: VS 2008 SP1 Designer glitch [debugged]
YAlexopoulos
10:15 1 Oct '08  
After attaching another VS 2008 to see the error it seems the cause is the following: [It seems it can't find the resource ScrollViewerStyle when opening it from designer.

{"Cannot find resource named '{ScrollViewerStyle}'. Resource names are case sensitive. Error at object 'sv' in markup file 'SpiderTreeControl;component/diagram/dragviewer.xaml' Line 8 Position 47."}

System.Windows.Markup.XamlParseException occurred
Message="Cannot find resource named '{ScrollViewerStyle}'. Resource names are case sensitive. Error at object 'sv' in markup file 'SpiderTreeControl;component/diagram/dragviewer.xaml' Line 8 Position 47."
Source="PresentationFramework"
LineNumber=8
LinePosition=47
NameContext="sv"
StackTrace:
at System.Windows.Markup.XamlParseException.ThrowException(String message, Exception innerException, Int32 lineNumber, Int32 linePosition, Uri baseUri, XamlObjectIds currentXamlObjectIds, XamlObjectIds contextXamlObjectIds, Type objectType)
at System.Windows.Markup.XamlParseException.ThrowException(ParserContext parserContext, Int32 lineNumber, Int32 linePosition, String message, Exception innerException)
at System.Windows.Markup.BamlRecordReader.ThrowExceptionWithLine(String message, Exception innerException)
at System.Windows.Markup.BamlRecordReader.ThrowException(String id, String parameter)
at System.Windows.StaticResourceExtension.ProvideValueInternal(IBamlReader bamlReader, Object targetObject, Object targetProperty, Boolean allowDeferredReference)
at System.Windows.StaticResourceExtension.ProvideValue(IServiceProvider serviceProvider)
at System.Windows.Markup.BamlRecordReader.ProvideValueFromMarkupExtension(MarkupExtension markupExtension, Object obj, Object member)
at System.Windows.Markup.BamlRecordReader.BaseReadOptimizedMarkupExtension(Object element, Int16 attributeId, PropertyDefinition propertyDefinition, Object value)
at System.Windows.Markup.BamlRecordReader.ReadPropertyWithExtensionRecord(BamlPropertyWithExtensionRecord bamlPropertyRecord)
at System.Windows.Markup.BamlRecordReader.ReadRecord(BamlRecord bamlRecord)
at System.Windows.Markup.BamlRecordReader.Read(Boolean singleRecord)
at System.Windows.Markup.TreeBuilderBamlTranslator.ParseFragment()
at System.Windows.Markup.TreeBuilder.Parse()
at System.Windows.Markup.XamlReader.LoadBaml(Stream stream, ParserContext parserContext, Object parent, Boolean closeStream)
at System.Windows.Application.LoadComponent(Object component, Uri resourceLocator)
at SpiderTreeControl.Diagram.DragViewer.InitializeComponent() in g:\Development\Components\Licenced\Freeware\NET 3.5\SpiderTreeControl\SpiderTreeControl\SpiderTreeControl\Diagram\DragViewer.xaml:line 1
at SpiderTreeControl.Diagram.DragViewer..ctor() in G:\Development\Components\Licenced\Freeware\NET 3.5\SpiderTreeControl\SpiderTreeControl\SpiderTreeControl\Diagram\DragViewer.xaml.cs:line 27
InnerException:
GeneralRe: VS 2008 SP1 Designer glitch [debugged]
Sacha Barber
11:24 1 Oct '08  
Most odd works for me, just tried at home as well.

Sorry.

You could have a search for that Resource, and move it to the UserControl.Resources section of the dragviewer.xaml file instead of where it is now. This may fix it for you

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralNice work guys!
mtonsager
4:46 28 Sep '08  
I really like how you used the mouse's velocity and animation. Cool!

Matt
GeneralRe: Nice work guys!
Sacha Barber
6:48 28 Sep '08  
Thanks man

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralGreat job Sacha and Fredrik
Dr.Luiji
11:15 26 Sep '08  
I liked the article indeed.
Well done.

Dr.Luiji
Trust and you'll be trusted.

GeneralRe: Great job Sacha and Fredrik
Sacha Barber
21:38 26 Sep '08  
Thanks man

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net

GeneralIt's Very nice.
mohammad
22:09 24 Sep '08  
Mr Sacha Barber
your Articles are best Articels.

First they ignore you;then they laugh at you ;then they fight you ;then you win

GeneralIt's Very nice.
mohammad
22:08 24 Sep '08  
Mr Sacha Barber
your Articles is best.

First they ignore you;then they laugh at you ;then they fight you ;then you win

GeneralRe: It's Very nice.
Sacha Barber
23:35 24 Sep '08  
Thanks

Sacha Barber
  • Microsoft Visual C# MVP 2008
  • Codeproject MVP 2008
Your best friend is you.
I'm my best friend too. We share the same views, and hardly ever argue

My Blog : sachabarber.net


Last Updated 21 Sep 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010