Custom Vertices?

Sep 11, 2009 at 11:01 AM

Good day,

Is is possible to make custom vertices on a IBidirectional graph.

The overloaded vertex should have a image, label and id.

Any thoughts on this?

Regards,

rootme

Sep 11, 2009 at 7:41 PM
Edited Sep 11, 2009 at 7:42 PM

Use a DataTemplate and/or a UserControl:

MyVertexRenderer.xaml

 

<UserControl 
     ... 
     x:Class="MyNamespace.MyVertexRenderer">
     <Grid>
          <TextBox Text="{Binding MyText}"/>
     </Grid>
</UserControl>

 

Then inside the VertexControl style in GraphEditor.xaml

 

<Style TargetType="{x:Type Controls:VertexControl}">
     <Setter Property="Template">
          <Setter.Value>
                 <ControlTemplate TargetType="{x:Type Controls:VertexControl}">
                      <Border ...>
                           <Grid>
                                <!-- ONE OF THE FOLLOWING: -->
                                <!-- 1, USE CONTROL DIRECTLY -->
	                        <mynamespace:MyVertexRenderer
                                     DataContext="{TemplateBinding Vertex}"/>
                                <!-- 2, USE DATATEMPLATE -->
                            	<ContentPresenter
                                     Content="{TemplateBinding Vertex}"/>
                           </Grid>
                      </Border>
                 ...

 

With "2", let's say you have a IBidirectionalGraph<MyVertex...> where MyVertex is your data class, next make a DataTemplate something like this inside the resources section of GraphEditor.xaml

 

<DataTemplate DataType="{x:Type mynamespace:MyVertex}">
     <mynamespace:MyVertexRenderer/>
</DataTemplate>

 

This will allow you to have multiple renderers for multiple data types; e.g. create an abstract class (MyVertex) for the generics (IBidirectionalGraph etc), then create your vertex classes (MyVertexTypeA, MyVertexTypeB...) then duplicate the last snippet altering the DataType tag to point the various data types and your renderer(s).

Good luck!

Sep 13, 2009 at 12:23 PM

Thank you for your reply ourben.

I think I see where this is going but I'm gonna ask anyway:

Currently I visualise my graph as a normal IBidirectional graph right?

  1. First I get the input from the user.
  2. Next I calculate relationships(edges) between the vertices.
  3. Finally I create the graph by adding the edges and vertices and set the graph as the source for the graphlayout.

Now for the questions:

  1. If I use my own Vertex user control, will the AddVertex still work or will I have to write an overload for it?
  2. If I create a User Control with Image, TextBox and id will I be able to just go eg. graph.Vertices[0].Image.Source="Image.gif", or the textbox text or whatever?
  3. More questions sure to come, just short on time at the moment.

Sorry if I sound like an idiot but I'm really trying to understand what I have to do so that I can do it myself, not just ask for someone else to do it for me.

Thank you in advance,

rootme

Sep 13, 2009 at 2:54 PM
Edited Sep 13, 2009 at 2:55 PM

When you AddVertex you act on the graph (QuickGraph) and not the visual (GraphSharp) representation. VertexControl is the class GraphSharp uses to render and layout the QuickGraph graph handed to it, which is why AddVertex takes a parameter of type TVertex where TVertex is defined in your implementation of the IBidirectionalGraph<TVertex, TEdge> generic.

If you create your graph as IBidirectional<string, IEdge<string, string>>

  • AddVertex takes a string
  • AddEdge takes an IEdge<string, string>, Edge<string, string>, or two strings (string, string)
  • GraphSharp uses the x:Type Controls:VertexControl template - as it would with any vertex type
  • The DataTemplate for string (if you created one in the resources section) will be used to render the content of VertexControl.Vertex (as it is a string)

The method I described doesn't really create a "new" vertex control, it just changes the content of the existing user control.

A tip is to make your base Vertex type a DependencyObject ancestor with the relevant DependencyProperty(ies) and then when graph.Vertices[0].MyProperty = value updates the QuickGraph graph, it will automatically propagate to the renderer (bypassing any GraphSharp classes - UserControl already gave the reference to your Vertex to your own vertex renderer) through WPF's DependencyProperty system.

These snippets (of the top of my head, forgive any syntax errors) should give you an idea what's going on:

 

<!-- with the style something like... -->
<DataTemplate DataType="{x:Type myns:MyVertexBase}">
   <Grid>
      <Label>{Binding Name}</Label>
   </Grid>
</DataTemplate>
<!-- OR... --> 

<DataTemplate DataType="{x:Type myns:MyVertexBase}">
   <myns:MyVertexRenderer/>
</DataTemplate>

<!-- ... and elsewhere ...-->

<UserControl ... x:Class="MyNamespace.MyVertexRenderer">
   <Grid>
      <Label>{Binding Name}</Label>
   </Grid>
</UserControl>

 

 

// a base vertex class something like
class MyVertexBase : DependencyObject {

   public static readonly DependencyProperty NameProperty =
      DependencyProperty.Register("Name", typeof(string), typeof(MyVertexBase);

   public string Name {
      get { return (string)GetValue(NameProperty); }
      set { SetValue(StringProperty, value); }

}

// elsewhere, after creating your graph
BidirectionalGraph<MyVertexBase, IEdge<MyVertexBase>> graph
    = SomeGraphCreatorMethod(...);

// add a vertex
graph.AddVertex(new MyVertexBase() { Name = "foo" } );

// change it
graph.Vertices[0].Name = "bar";

 

Sep 13, 2009 at 3:01 PM
Edited Sep 13, 2009 at 3:01 PM

PS: The properties don't actually *need* to be DependencyProperty when using DataTemplate, but if you come to refactor later and a new renderer doesn't use DataTemplate, you'll curse having to write in all the public static readonly should-be-sugar... so much so you might find find yourself advising everyone to do it all the time (like I am, woe is me) :-)

Sep 14, 2009 at 11:54 AM
Edited Sep 14, 2009 at 12:18 PM

Hi ourben,

I attempted to do as you told(No changes or alterations) but I get the following error when I do this:

 

var g = new BidirectionalGraph<MyVertexBase, IEdge<MyVertexBase>>();

Image i = new Image();

g.AddVertex(new MyVertexBase() { Name = "foo" , Image = i});

graphLayout.Graph = g;

 

Error    1    Cannot implicitly convert type 'QuickGraph.BidirectionalGraph<WpfApplication1.MyVertexBase,QuickGraph.IEdge<WpfApplication1.MyVertexBase>>' to 'QuickGraph.IBidirectionalGraph<object,QuickGraph.IEdge<object>>'. An explicit conversion exists (are you missing a cast?)

On to other things...

The error:

 

<DataTemplate DataType="{x:Type thisApp:MyVertexBase}">
<Grid>
<Label>{Binding Name}</Label>
<Image>{Binding Image}</Image>
</Grid>
</DataTemplate>
Error    1    The Element type 'System.Windows.Controls.Image' does not have an associated TypeConverter to parse the string '{Binding Image}'.

I am quite new to the DependencyProperties so I'm trying to understand them.

Thank's for all the help!

rootme
Sep 14, 2009 at 3:01 PM
Edited Sep 14, 2009 at 3:04 PM

Sorry, I neglected to mention, you want to create a bare class to wrap the GraphLayout generic (this is what GraphEditor in the download example does)

 

public class MyGraphLayout
   : GraphSharp.Controls.GraphLayout<MyVertexBase, MyEdgeBase, MyGraph>
{ /* nothing to do here... */ }

 

Then in the GraphEditor.mxml (or wherever you declare the layout in MXML), swap <Controls:GraphLayout*..> for something like<mynamespace:MyGraphLayout..>

note: MyEdgeBase, again a bare class that inherits from Edge<MyVertexBase> and MyGraph is a bare class that inherits from BidirectionalGraph<MyVertexBase, MyEdgeBase>

As for the dependency properties, I'm not sure (I'm new to WPF too), but I'd hazard a guess that error wouldn't happen if replaced <Image>{Binding ...}</Image> for <Image Source="{Binding ...}"/>

Let me know how it goes!

 

* edit: it's Controls:GraphLayout, not Controls:GraphEditor as originally posted... my bad.

Sep 15, 2009 at 7:14 AM

Ok, I think were getting closer...

public class MyEdgeBase : QuickGraph.Edge<MyVertexBase>
    {
        /* nothing to do here... */ 
    }

Gives me this error: 'QuickGraph.Edge<WpfApplication1.MyVertexBase>' does not contain a constructor that takes '0' arguments

So here is MyVertexBase:

// a base vertex class something like
    public class MyVertexBase : DependencyObject 
    {
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(MyVertexBase));

        public string Name 
        {
          get { return (string)GetValue(NameProperty); }
          set { SetValue(NameProperty, value); }
        }
        
        public static readonly DependencyProperty ImageProperty =
           DependencyProperty.Register("Image", typeof(Image), typeof(MyVertexBase));

        public Image Image
        {
            get { return (Image)GetValue(ImageProperty); }
            set { SetValue(ImageProperty, value); }
        }
    }   

I dont understand how to pass the dependency object arguments when the class is being inherited???

I tried putting a constructor like:

public MyVertexBase()
{

}

intside the class but no joy.

Finally with MyGraphLayout: Will I still be able to specify the LayoutAlgortihm ect.

Thanks again for your patience and sharing your knowlege with me!

Cheers,

rootme

Sep 15, 2009 at 11:36 AM

I'm not in the office at the moment, but I'd guess the 0 argument constructor error is happening because you are trying to create an edge programmatically. Here is from memory what I have:

public /*abstract*/ class MyEdgeBase : Edge<MyVertexBase>
{
   // bare constructor
   public MyEdgeBase(MyVertexBase fromvertex, MyVertexBase tovertex)
      : base(fromvertex, tovertex)
   {
   }
}

That said, your error does say you are calling the ctor with no parameters, so double the line number associated with the error.

When I create an Edge I use:

var verta = new MyVertexBase();
var vertb = new MyVertexBase();
// create an edge like
var edge = new MyEdgeBase(verta, vertb);
graph.AddEdge(edge);
// or
graph.AddEdge(verta, vertb);

The code for MyVertexBase looks fine to me.  Perhaps I don't understand the question?

As for the final query, yes, it works just the same as Controls:GraphLayout - all the parameters (attributes in xaml) are members on the GraphLayout<a,b,c> ancestor.

Sep 15, 2009 at 1:34 PM

Ahh... Of course... Im an idiot...

 public MyEdgeBase(MyVertexBase fromvertex, MyVertexBase tovertex)
      : base(fromvertex, tovertex)
   {
   }

Everything else seems fine except I get this Unknown error:

Unknown build error, 'GenericArguments[1], 'WpfApplication1.MyEdgeBase', on 'GraphSharp.Algorithms.Highlight.IHighlightController`3[TVertex,TEdge,TGraph]' violates the constraint of type parameter 'TEdge'. Line 145 Position 30.'

Maybe a inherited class for the IHighlightController?

And finally, you were right, not that I doubted you, the new GraphLayout control works as normal now that the constructor is sorted out.

Sep 15, 2009 at 1:52 PM

I'm not sure about that one?

When I was using the latest source before Contracts was removed I sometimes got weird errors but rebuild made them disappear.

Your edge type inherits from Edge which is a QuickGraph type and it definitely implements IEdge (the only constraint it could complain about).

QuickGraph Edge http://quickgraph.codeplex.com/sourcecontrol/changeset/view/36564?projectName=quickgraph#280638

GraphSharp IHighlightController http://graphsharp.codeplex.com/sourcecontrol/changeset/view/28030?projectName=graphsharp#441880

If you have no luck tracking the bug down, put your GraphLayout, Vertex and Edge class source on here or a pastie and I'll see if anything stands out.

Sep 15, 2009 at 2:08 PM

Ok here it is:

public class MyGraphLayout : GraphSharp.Controls.GraphLayout<MyVertexBase, MyEdgeBase, MyGraph>
    { 
        /* nothing to do here... */ 
    }



public class MyVertexBase : DependencyObject { public static readonly DependencyProperty NameProperty = DependencyProperty.Register("Name", typeof(string), typeof(MyVertexBase)); public string Name { get { return (string)GetValue(NameProperty); } set { SetValue(NameProperty, value); } } public static readonly DependencyProperty ImageProperty = DependencyProperty.Register("Image", typeof(Image), typeof(MyVertexBase)); public Image Image { get { return (Image)GetValue(ImageProperty); } set { SetValue(ImageProperty, value); } } }

public class MyEdgeBase : Edge<MyVertexBase> { /* nothing to do here... */ public MyEdgeBase(MyVertexBase fromvertex, MyVertexBase tovertex) : base(fromvertex, tovertex) { } }

Ok. In what time zone do you live? I just want to know so that I roughly know what time to expect replies(i.e. not while you are sleeping)

Again, thank you very much for everything.

Sep 16, 2009 at 6:45 AM

Ok. I sorted out the Unknow error.

Now I get this:

var g = new BidirectionalGraph<MyVertexBase, IEdge<MyVertexBase>>();
            
Image i = new Image();
i.Source = "Individual.gif"; g.AddVertex(new MyVertexBase() { Name = "Bob" , Image = i }); graphLayout.Graph = g;

Cannot implicitly convert type 'QuickGraph.BidirectionalGraph<WpfApplication1.MyVertexBase,QuickGraph.IEdge<WpfApplication1.MyVertexBase>>' to 'WpfApplication1.MyGraph'

Here is MyGraph aswell:

public class MyGraph : QuickGraph.BidirectionalGraph<MyVertexBase, MyEdgeBase>
    {
        /* nothing to do here... */ 
    }

 

Regards,

rootme

Sep 18, 2009 at 1:03 PM

Anyone? Please, Im stuck. :(

 

Coordinator
Sep 18, 2009 at 3:06 PM

Try this one:

 

var g = new MyGraph();
Sep 18, 2009 at 3:57 PM

Ahh... Of course!

Thank you so much palesz, but now I still have one last problem, if you could spare me a moment of your time.

When I do:

var g = new MyGraph();
            
Image i = new Image();

//Set the source of the image to a valid source...

g.AddVertex(new MyVertexBase() { Name = "Bob" , Image = i });


graphLayout.Graph = g;

The graph layout only display's text showing "{Binding Name}". No image or "Bob".

Any thoughts on what I am doing wrong?

Coordinator
Sep 19, 2009 at 8:01 AM

That's because you're using the {Binding ...} syntax at the wrong place. Try this template:

 

<DataTemplate DataType="{x:Type thisApp:MyVertexBase}">
            <Grid>
                <Label Text="{Binding Name}"></Label>
                <Image Source="{Binding Image}"></Image>
            </Grid>
</DataTemplate>

 

 

Sep 19, 2009 at 10:19 AM

Thank you for the reply palesz.

Just 2 things:

For anyone else following this Label does not have a property Text. It is Content.

And my Image still wont show :/

Any ideas why?

Coordinator
Sep 19, 2009 at 3:10 PM

Ok, then try this template (because how i see, you are creating Image objects in your MyVertexBase):

 

<DataTemplate DataType="{x:Type thisApp:MyVertexBase}">
            <Grid>
                <Label Content="{Binding Name}"></Label>
                <ContentControl Content="{Binding Image}"></ContentControl>
            </Grid>
</DataTemplate>
Sep 19, 2009 at 6:16 PM

Yay.... It works! Just edited the template, otherwise the text is behind the image and I want it below.

<DataTemplate DataType="{x:Type thisApp:MyVertexBase}">
            <Grid>
            <StackPanel>
                <ContentControl Content="{Binding Image}"></ContentControl>
                <Label Content="{Binding Name}"></Label>
            </StackPanel>
            </Grid>
</DataTemplate>

Also added horizontal alignment center for both.

Thank you so much for your guidance palesz!

Aug 31, 2010 at 6:06 PM

@rootme It would be great if you would post your final code and maybe summarize what you learned so that others can benefit from the long training session presented by these posts.

Sep 15, 2010 at 1:38 AM

Yes please!  I'm trying my best to duplicate what rootme achieved, but I'm having a hard time piecing everything together.  If rootme has left the thread for good, would someone else be willing to put together some sort of a walkthrough on this?

Oct 15, 2010 at 4:11 AM

Here's what I am using (and it's working so far):

I have the following saved as MyGraphLayout.cs


    public class MyGraph : GraphSharp.SoftMutableBidirectionalGraph<MyVertexBase, MyEdgeBase>
    {
        /* nothing to do here... */
    }


    public class MyGraphLayout : GraphSharp.Controls.GraphLayout<MyVertexBase, MyEdgeBase, MyGraph>
    {
        /* nothing to do here... */
    }

    public class MyVertexBase : DependencyObject
    {
        public static readonly DependencyProperty NameProperty =
            DependencyProperty.Register("Name", typeof(string), typeof(MyVertexBase));

        public string Name
        {
            get { return (string)GetValue(NameProperty); }
            set { SetValue(NameProperty, value); }
        }

        public static readonly DependencyProperty ImageProperty =
           DependencyProperty.Register("Image", typeof(Image), typeof(MyVertexBase));

        public Image Image
        {
            get { return (Image)GetValue(ImageProperty); }
            set { SetValue(ImageProperty, value); }
        }
    }

    public class MyEdgeBase : Edge<MyVertexBase>
    {
        /* nothing to do here... */

        public MyEdgeBase(MyVertexBase fromvertex, MyVertexBase tovertex)
            : base(fromvertex, tovertex)
        {

        }

    }


Then I've got the following XAML in a UserControl (it also shows a sample tooltip and context menu):

<UserControl x:Class="Relations.Views.MapViewerControl"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:zoom="clr-namespace:WPFExtensions.Controls;assembly=WPFExtensions"
             xmlns:thisApp="clr-namespace:Relations"
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             mc:Ignorable="d" 
             d:DesignHeight="300" d:DesignWidth="300">
    <Grid>
        <Grid.Resources>
            <!-- template for a grid vertex -->
            <DataTemplate DataType="{x:Type thisApp:MyVertexBase}">
                <Border BorderThickness="3" CornerRadius="6" Padding="3" BorderBrush="#FF900404">
                    <Border.ContextMenu>
                        <ContextMenu>
                            <MenuItem Header="Link To" Click="MenuItem_Click" Tag="{Binding}" />
                        </ContextMenu>
                    </Border.ContextMenu>
                    <Border.ToolTip>
                        <ToolTip>
                            <TextBlock Text="{Binding Name, StringFormat='Tooltip for {0}'}" />
                        </ToolTip>
                    </Border.ToolTip>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition />
                            <RowDefinition />
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition />
                            <ColumnDefinition />
                        </Grid.ColumnDefinitions>
                        <Image Source="{Binding Image}" Height="24" Grid.Row="0" Grid.Column="0" /> 
                        <TextBlock Grid.Row="0" Grid.Column="1" Text="{Binding Name}" FontSize="14" FontWeight="Black" />
                        <TextBlock Text="Other stuff goes here..." FontSize="10" Grid.Row="1" Grid.ColumnSpan="2" />
                    </Grid>
                </Border>
            </DataTemplate>
        </Grid.Resources>
        <zoom:ZoomControl AllowDrop="True" Drop="DropHandler"  >
            <thisApp:MyGraphLayout Graph="{Binding GraphToVisualize}"
                                   LayoutAlgorithmType="FR"
                                   OverlapRemovalAlgorithmType="FSA"
                                   HighlightAlgorithmType="Simple"                                   
                                   AsyncCompute="true"
                                   ShowAllStates="false" />
        </zoom:ZoomControl>
    </Grid>
</UserControl>

Then I've got the following code behind (you'll see the drop side of Drag & Drop that I'm using to add verticies dynamically):


    public partial class MapViewerControl : UserControl
    {
        MapViewerViewModel viewModel;

        public MapViewerControl()
        {
            viewModel = new MapViewerViewModel();
            InitializeComponent();
            Loaded += new RoutedEventHandler(MapViewerControl_Loaded);
        }

        void MapViewerControl_Loaded(object sender, RoutedEventArgs e)
        {
            DataContext = viewModel;
        }

        private void DropHandler(object sender, DragEventArgs e)
        {
            Data.Object objToAdd = e.Data.GetData(typeof(Data.Object)) as Data.Object;
            if (objToAdd != null)
            {
                viewModel.AddVertex(objToAdd);
            }
        }

        private void MenuItem_Click(object sender, RoutedEventArgs e)
        {
            var menuItem = sender as MenuItem;
            if (menuItem != null && menuItem.Tag != null)
            {
                var vertex = menuItem.Tag as MyVertexBase;
                if (vertex != null)
                {
                    MessageBox.Show("You want to link " + vertex.Name + " to ...?");
                }
            }
        }

And here's the View Model that I'm using (I'm using the MvvmLight framework, that's where ViewModelBase comes from):

public class MapViewerViewModel : ViewModelBase
    {
        public MapViewerViewModel()
        {
            GraphToVisualize = new MyGraph();            
        }

        private MyGraph _graph;

        public MyGraph GraphToVisualize
        {
            get { return _graph; }
            set { _graph = value; RaisePropertyChanged("GraphToVisualize"); }
        }

        public void AddVertex(Data.Object objectToAdd)
        {
            GraphToVisualize.AddVertex(new MyVertexBase() { Name = objectToAdd.ObjName });
        }        
    }

This example relies on adding objects / verticies (whatever you want to call them) via the AddVertex() call on my ViewModel (which I get to via drag & drop on the user control), but you could obviously add your verticies however you like.
One thing that I don't like about my method (that will be the next thing I fix) is that new verticies show up right on top of eachother.  It'd be nice if they neatly landed next to eachother.
Anyway, this should be a good enough end-to-end example to get someone up and running with a custom vertex.
Oct 12, 2011 at 11:07 AM

hi !

i have an abstract class and x classes inheriting from this one.

ive tried to make a datatemplate for each of these classes, but wpf only displays the border with the vertexname in it.

any suggestions how i can make wpf use the datatemplate instead ?

 

      <ControlTemplate TargetType="{x:Type Controls:VertexControl}">
                      <Border ...>
                           <Grid>
                                <!-- ONE OF THE FOLLOWING: -->
                                <!-- 1, USE CONTROL DIRECTLY -->
                            <ContentPresenter
                                     Content="{TemplateBinding Vertex}"/>
                           </Grid>
                      </Border>