One question about RadDiagram MVVM

18 posts, 1 answers
  1. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 07 Mar 2015 Link to this post

    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
  2. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 09 Mar 2015 in reply to Jackey Link to this post

    Hi team,
    It seems that i can't use Custom Shape with MVVM. Are there any demos to show that usage?
  3. UI for WPF is Visual Studio 2017 Ready
  4. Mahmut
    Mahmut avatar
    22 posts
    Member since:
    Nov 2014

    Posted 10 Mar 2015 in reply to Jackey Link to this post

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

  5. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 12 Mar 2015 in reply to Mahmut Link to this post

    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? 
  6. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 13 Mar 2015 in reply to Jackey Link to this post

    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
  7. Pavel R. Pavlov
    Admin
    Pavel R. Pavlov avatar
    1182 posts

    Posted 16 Mar 2015 Link to this post

    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.

     
  8. Mahmut
    Mahmut avatar
    22 posts
    Member since:
    Nov 2014

    Posted 18 Mar 2015 in reply to Jackey Link to this post

    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.
  9. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 30 Mar 2015 in reply to Mahmut Link to this post

    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

  10. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 30 Mar 2015 in reply to Mahmut Link to this post

    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
  11. Mahmut
    Mahmut avatar
    22 posts
    Member since:
    Nov 2014

    Posted 31 Mar 2015 in reply to Jackey Link to this post

    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 :)))
  12. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 31 Mar 2015 in reply to Mahmut Link to this post

    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
  13. Mahmut
    Mahmut avatar
    22 posts
    Member since:
    Nov 2014

    Posted 31 Mar 2015 in reply to Jackey Link to this post

    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.
  14. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 31 Mar 2015 in reply to Mahmut Link to this post

    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);
            }
            ...
        }
  15. Answer
    Mahmut
    Mahmut avatar
    22 posts
    Member since:
    Nov 2014

    Posted 01 Apr 2015 in reply to Jackey Link to this post

    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 }));
                }
            }
        }
       
    }
  16. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 01 Apr 2015 in reply to Mahmut Link to this post

    Hi Mahmut,
    The correct extension class works great. Thanks for your help!

    Thanks,
    Jingfei
  17. Jackey
    Jackey avatar
    71 posts
    Member since:
    Oct 2014

    Posted 02 Apr 2015 in reply to Mahmut Link to this post

    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
  18. Mahmut
    Mahmut avatar
    22 posts
    Member since:
    Nov 2014

    Posted 22 Apr 2015 in reply to Jackey Link to this post

    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.

  19. Bekzod
    Bekzod avatar
    2 posts
    Member since:
    Mar 2015

    Posted 27 Apr 2015 in reply to Pavel R. Pavlov Link to this post

    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?

Back to Top
UI for WPF is Visual Studio 2017 Ready