This is a migrated thread and some comments may be shown as answers.

One question about RadDiagram MVVM

17 Answers 439 Views
Diagram
This is a migrated thread and some comments may be shown as answers.
Jackey
Top achievements
Rank 1
Jackey asked on 08 Mar 2015, 05:38 AM
Hi team,
First I have to say RadDiagram is a quite charming control/platform for diagramming. But it's also a little complicated to master it. Now I have a question about MVVM demo for RadDiagram. The demo defines a shape style shown below.
<telerik:RadDiagram.ShapeStyle>
    <Style TargetType="telerik:RadDiagramShape">
        <Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
    </Style>
</telerik:RadDiagram.ShapeStyle
The shape style would be applied to the shape in the diagram. Now I have two different shapes. Waht should I do now? I guess I can't duplicate the above code. I mess myself with custom shape, customized content template and etc. I want to know the recommended way to do this.

Thanks,
Jingfei

17 Answers, 1 is accepted

Sort by
0
Jackey
Top achievements
Rank 1
answered on 09 Mar 2015, 02:52 PM
Hi team,
It seems that i can't use Custom Shape with MVVM. Are there any demos to show that usage?
0
Mahmut
Top achievements
Rank 1
answered on 10 Mar 2015, 11:41 AM
You can use ShapeStyleSelector, or you use one style, and define content templates.
This is example for one style and content templates. I hope it is helpful.

<DataTemplate x:Key="Shape1Template">
    <StackPanel Height="Auto" Width="Auto">
        <Rectangle Width="25" Height="25" Fill="White" Stroke="Black"/>
    </StackPanel>
</DataTemplate>
 
<DataTemplate x:Key="Shape2Template">
    <StackPanel>
        <Ellipse Width="25" Height="25" Fill="White" Stroke="Black"/>
    </StackPanel>
</DataTemplate>

<Style x:Key="ItemStyle" TargetType="telerik:RadDiagramShape">
    <Setter Property="Position" Value="{Binding Position, Mode=TwoWay}" />
    <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
    <Setter Property="Height" Value="{Binding Height, Mode=TwoWay}" />
    <Setter Property="Width" Value="{Binding Width, Mode=TwoWay}" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="IsEditable" Value="False" />
    <Setter Property="StrokeThickness" Value="0" />
    <Setter Property="UseDefaultConnectors" Value="True"/>
    <Setter Property="ContentTemplate" Value="{Binding Converter={StaticResource TemplateConverter}}" />
</Style>

public class TemplateConverter : IValueConverter
{
    System.Windows.DataTemplate template1, template2;
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if (value != null)
        {
            if (value is Shape1)
                return template1;
            else if(value is Shape2)
                return template2;
        }
            return value;
    }
 
    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        return null;
    }
}

0
Jackey
Top achievements
Rank 1
answered on 12 Mar 2015, 02:22 PM
Hi Mahmut,
Thanks for your reply. I am glad to try the ShapeStyleSelector. However, I want to create custom connectors. How to integrate them with MVVM? 
0
Jackey
Top achievements
Rank 1
answered on 13 Mar 2015, 07:53 AM
Hi Mahmut,
I provide more details. According to Custom Connectors, we could create custom connectors like this
var shape = new RadDiagramShape();
var connector = new RadDiagramConnector(){Offset = new Point(1, 0.67), Name = "CustomConnector1"};
shape.Connectors.Add(connector);

However, per my understanding, in MVVM of RadDiagram, we only have ViewModel objects, and no Connectors property exists in NodeViewModelBase. So how should I do this? 

Also for connection points of RadDiagramConnection, where can I specify extra connection points in LinkViewModelBase?

Thanks,
Jingfei
0
Pavel R. Pavlov
Telerik team
answered on 16 Mar 2015, 09:33 AM
Hi Jingfei,

I understand your requirement. Unfortunately the current implementation of the RadDiagram does not support defining connectors in full MVVM scenario.

However, there is a way to predefine all the connectors of a shape and to apply them to different shapes based on a property exposed by your business item. Here is what you can do.

You can create a custom collection (e.g. CustomConnectorCollection) deriving from a List<RadDiagramConnector>. Once you define that collection you will be able to create an instance in XAML as resource. Once you define all the connectors that you need to visualize you are ready to apply them to the RadDiagramShapes.

To do this you can subscribe to the StatusChanged event of the ContainerGenator of the RadDiagram. Once the status is ContainersGenerated you will be able to change all the connectors of all shapes. Furthermore, you need to traverse all business items and detect which ones will use the newly defined CustomConnectorCollection. When you find an item that need to visualize your custom connectors, you can get its container (RadDiagramShape) using the ContainerGenerator. After that you need to fill the Connectors collection of the corresponding shape. Please keep in mind that the Name property of the RadDiagramConnectors should be all different. This is why it is a good idea to define those names in a unique way.

I prepared a sample project demonstrating the described approach. Please take a look at it and let us know if you need any further assistance.

Regards,
Pavel R. Pavlov
Telerik
 

Check out the Telerik Platform - the only platform that combines a rich set of UI tools with powerful cloud services to develop web, hybrid and native mobile apps.

 
0
Mahmut
Top achievements
Rank 1
answered on 18 Mar 2015, 11:39 AM
Hi Jingfei,
I had this problem, and I solved the problem with help of telerik team. If you create connectors dynamically, you must your own connector model which hold data is necessary for creating connectors. My own model is in below.

public class ConnectorModel : ObservableObject
   {
       string name;
       Point offset;
 
       public string Name
       {
           get { return name; }
           set { name = value; RaisePropertyChanged(() => Name); }
       }
 
       public Point Offset
       {
           get { return offset; }
           set { offset = value; RaisePropertyChanged(() => Offset); }
       }
 
       public ConnectorModel()
       {
 
       }
 
       public ConnectorModel(string name, Point offset)
       {
           this.name = name;
           this.offset = offset;
       }
 
       public override string ToString()
       {
           return  this.name;
       }

And after that you define a extension class which will convert your connector models to RadDiagramConnectors.

public class ConnectorExtension
{
 
    public static readonly DependencyProperty ConnectorsProperty =
        DependencyProperty.RegisterAttached("Connectors", typeof(List<ConnectorModel>), typeof(ConnectorExtension), new UIPropertyMetadata(null, new PropertyChangedCallback(ConnectorsChanged)));
 
 
 
    public static void SetConnectors(UIElement element, List<ConnectorModel> value)
    {
        element.SetValue(ConnectorsProperty, value);
    }
    public static List<ConnectorModel> GetConnectors(UIElement element)
    {
        return (List<ConnectorModel>)element.GetValue(ConnectorsProperty);
    }
 
 
    public static void ConnectorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var connectors = e.NewValue as List<ConnectorModel>;
        var shape = d as RadDiagramShapeBase;
        if (shape != null )
        {
            shape.UseDefaultConnectors = false;
            shape.Connectors.Clear();
            if (connectors != null)
            {
                connectors.ForEach(connector => shape.Connectors.Add(new RadDiagramConnector() { Offset = connector.Offset, Name = connector.Name, Tag=connector.Name }));
            }
        }
    }
  
}

And my style is in below. If you want you create a UseDefaultConnectors property in your NodeModel. I handle it in Extension class. If you want your own connectors, then property of RadDiagramShape called UseDefaultConnectors must be false.

<Style x:Key="DynamicConnectorStyle" TargetType="telerik:RadDiagramShape">
    <!--<Setter Property="Position" Value="{Binding Position}" />-->
    <Setter Property="Position" Value="{Binding Position, Mode=TwoWay}"/>
    <Setter Property="IsSelected" Value="{Binding IsSelected}" />
    <Setter Property="Height" Value="{Binding Height}" />
    <Setter Property="Width" Value="{Binding Width}" />
    <Setter Property="Background" Value="Transparent" />
    <Setter Property="IsEditable" Value="False" />
    <Setter Property="StrokeThickness" Value="0" />
    <Setter Property="extensions:ConnectorExtension.Connectors" Value="{Binding Path=Connectors,UpdateSourceTrigger=PropertyChanged}"/>
    <Setter Property="ContentTemplate" Value="{Binding Converter={StaticResource DataTemplateConverter}}" />
</Style>


and if you use ShapeStyleSelector, and you try to get style object from application resources  or frameworkelement resources. I recommend you to use cache mechanism. In my project, I get style and template from Application Resources and sometimes raddiagram was working slowly. After that, I defined the problem and I created a simple cache mechanism with Dictionary object. It works good.
0
Jackey
Top achievements
Rank 1
answered on 30 Mar 2015, 01:00 PM
Hi Mahmut,
Thanks a lot for your reply. But when i use your code, the project can't compile correclty. It gives me following errors

Error 21 Cannot resolve the Style Property 'Connectors'. Verify that the owning type is the Style's TargetType, or use Class.Property syntax to specify the Property. Line 87 Position 21. D:\Workspaces\demoproject\App.xaml 87 21 BC7100.Shell

Any clues of what's happening? Or do you have a runnable project which can be shared with me? Thanks again.

Jingfei

0
Jackey
Top achievements
Rank 1
answered on 30 Mar 2015, 01:17 PM
Hi Mahmut,
The compile error is my bad. After i fix it, it compiles with no problem. But it throws InvalidOperationException when debugging the project.

The default connectors cannot be removed because connections are attached to it.

I know it might due to I use a Link view model like this
public class Link : LinkViewModelBase<NodeViewModelBase>
    {
        public Link()
            : base()
        { }
 
        public Link(NodeViewModelBase source, NodeViewModelBase target, SubLine.Kinds kind)
            : base(source, target)
        {
            this.Kind = kind;
        }
 
        public SubLine.Kinds Kind { get; set; }
    }
I need to create links between nodes. So what am i gonna do with the custom connectors under the scope of MVVM?

Thanks,
Jingfei
0
Mahmut
Top achievements
Rank 1
answered on 31 Mar 2015, 06:36 AM
Hi Jingfei,
throw this exception when it cannot find any connector to connect objects. So be sure, before bind your viewmodel your, items are assigned to viewmodel. Actually assign items before the connections. And bind your viewmodel to RadDiagram. I hope it works :)))
0
Jackey
Top achievements
Rank 1
answered on 31 Mar 2015, 07:51 AM
Hi Mahmut,
Sorry for my bad English. I am not fully understanding your answer. It seems that I can't remove the default connectors because there are already connections attached to them. I can temporarily remove the connections generated by Link view model. However, ultimately, I need the connections established in some way. So how am I gonna do this? No documentation to brief that. I would be quite appreciated that you can provide me a demo runnable project. :)

Thanks,
Jingfei
0
Mahmut
Top achievements
Rank 1
answered on 31 Mar 2015, 11:09 AM
Error message says that,but if there is no any connector can be attached, then again throw same exception. For example, you define a custom connector is called "Left1", you create a connection to attach to this connector. When you add the connection to GraphSource and if your connector "Left1" is not created yet,then it throws same exception. So be sure your custom connectors are created, in basic way remove all connections. If you share some codes, maybe I can help you. Your model has custom connectors, your GraphSource, xaml,styles etc.

Regards.
0
Jackey
Top achievements
Rank 1
answered on 31 Mar 2015, 12:48 PM
The code of my graph view model is like.
The core logic is the method PopulateGraph. Inside this method, a bunch of nodes (which is a instance of node view model) and their custom connectors would be created first. I use style selectors to map different styles to three different nodes. The point is I comment the CreateLinks invocation. Because with this line, the following exception â€‹The default connectors cannot be removed because connections are attached to it. would occur. So after removing it, how am I gonna establish the connections between each nodes? Also some connections need specific connection points. Hope make myself clear! Thanks.
    public class LineGraph : ObservableGraphSourceBase<NodeViewModelBase, Link>
    {
        ...
 
        public void PopulateGraph(Line line)
        {
            var startNode = LineGraph.CreateLineNode(line.StartNodeType);
            startNode.Position = new Point(LineGraph.StartNodeXPosition, LineGraph.LineNodeYPosition);
            startNode.Width = LineGraph.LineNodeWidth;
            startNode.Height = LineGraph.LineNodeHeight;
            // NOTE: this line of code is creating custom connectors
            startNode.Connectors = LineGraph.CreateNodeConnectors(startNode.NodeType);
            this.AddNode(startNode);
             
            // Create gateway nodes and the links between the start node
            var gatewayNodes = this.CreateWirelessGatewayNodes();
            foreach (var gatewayNode in gatewayNodes)
            {
                // NOTE: this line of code is creating custom connectors
                gatewayNode.Connectors = LineGraph.CreateGatewayNodeConnector(gatewayNode);
            }
            
            // Create section nodes
            var sectionNodes = this.CreateSectionNodes(line);
            foreach (var sectionNode in sectionNodes)
            {
                // NOTE: this line of code is creating custom connectors
                sectionNode.Connectors = LineGraph.CreateSectionNodeConnector(sectionNode);
            }
 
            // Create end node
            var endnode = LineGraph.CreateLineNode(line.EndNodeType);
            endnode.Position = new Point(LineGraph.GetSectionNodeX(line.SectionCount) + StartConnectionHorizontalWidth, LineGraph.LineNodeYPosition);
            endnode.Width = LineGraph.LineNodeWidth;
            endnode.Height = LineGraph.LineNodeHeight;
            // NOTE: this line of code is creating custom connectors
            endnode.Connectors = LineGraph.CreateNodeConnectors(endnode.NodeType);
            this.AddNode(endnode);
 
            // Commented below to avoid following exception
            // <exception>The default connectors cannot be removed because connections are attached to it </exception>
//            this.CreateLinks(startNode, gatewayNodes, sectionNodes, endnode);
        }
        ...
    }
0
Accepted
Mahmut
Top achievements
Rank 1
answered on 01 Apr 2015, 08:15 AM
Hi Jingfei,
If you used my ConnectorExtension, then it is my fault, because I noticed that I send you my old extension class. I'm sorry about that. You must clear connectors before you assign UseDefaultConnector property false.

Regards
Correct Extension Class
public class ConnectorExtension
{
  
    public static readonly DependencyProperty ConnectorsProperty =
        DependencyProperty.RegisterAttached("Connectors", typeof(List<ConnectorModel>), typeof(ConnectorExtension), new UIPropertyMetadata(null, new PropertyChangedCallback(ConnectorsChanged)));
  
  
  
    public static void SetConnectors(UIElement element, List<ConnectorModel> value)
    {
        element.SetValue(ConnectorsProperty, value);
    }
    public static List<ConnectorModel> GetConnectors(UIElement element)
    {
        return (List<ConnectorModel>)element.GetValue(ConnectorsProperty);
    }
  
  
    public static void ConnectorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var connectors = e.NewValue as List<ConnectorModel>;
        var shape = d as RadDiagramShapeBase;
        if (shape != null )
        {
            shape.Connectors.Clear();
            shape.UseDefaultConnectors = false;
            if (connectors != null)
            {
                connectors.ForEach(connector => shape.Connectors.Add(new RadDiagramConnector() { Offset = connector.Offset, Name = connector.Name, Tag=connector.Name }));
            }
        }
    }
   
}
0
Jackey
Top achievements
Rank 1
answered on 01 Apr 2015, 12:42 PM
Hi Mahmut,
The correct extension class works great. Thanks for your help!

Thanks,
Jingfei
0
Jackey
Top achievements
Rank 1
answered on 02 Apr 2015, 02:49 PM
Hi Mahmut,
I come across a problem when using your way to create custom connectors. I need to create a graph source, and then fill the nodes and links, and custom connectors with special offsets (say one of them is (0.2,0.2). And later on, i need to add a node, the code i use is like following
if (this._graphSource == null)
 {
     this._graphSource = new LineGraph();
 }
 
 this._graphSource.Clear();
 this._graphSource.PopulateGraph(this);
 return this._graphSource;

ANd then the custom offset value doesn't work again. If I totally create a new graph source each time i need to add nodes or remove nodes, then the custom offset would remain work. Any ideas of what's happening?

Thanks,
Jingfei
0
Mahmut
Top achievements
Rank 1
answered on 22 Apr 2015, 08:05 AM

Hi Jingfei,

You have to create your item and your custom connectors. After that, you can create your connections with custom connectors. Could you share last version of PopulateGraph function with nodes and connections creation functions?

And if your connectors are static why you dont create inside the objects? 
It is possible you forget to create custom connectors in somewhere in your code.

0
Bekzod
Top achievements
Rank 1
answered on 28 Apr 2015, 04:21 AM

Hi all,

I created CustomConnnector but i can not use mvvm.

diagram.AddConnection(fkShape, pkShape, "Right", "Left")

It is working correctly but i didn't find any property for connectors in mvvm? 

var customLink = new LinkViewModelBase<NodeViewModelBase>(fkModel, pkModel)

How can i attach connectors for this link?

Tags
Diagram
Asked by
Jackey
Top achievements
Rank 1
Answers by
Jackey
Top achievements
Rank 1
Mahmut
Top achievements
Rank 1
Pavel R. Pavlov
Telerik team
Bekzod
Top achievements
Rank 1
Share this question
or