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
>
Thanks,
Jingfei
17 Answers, 1 is accepted

It seems that i can't use Custom Shape with MVVM. Are there any demos to show that usage?

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

Thanks for your reply. I am glad to try the ShapeStyleSelector. However, I want to create custom connectors. How to integrate them with MVVM?

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

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.

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

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
; }
}
Thanks,
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 :)))

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

Regards.

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

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

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

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

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.

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?