Is there a way to rotate child shapes inside of a rotating container? The idea is to keep the children of the container laid out exactly the same as the container is rotated. An additional complication is that I need to be able to select the child shapes.
This second requirement is tricky because it essentially eliminates the easy solutions -- i.e. Ellipses/Rects inside the container instead of RadDiagramShapes.
Right now, my idea is to hook the OnRotationAngleChanged method in the RadDiagramContainer and then iterate through the children and apply a RotateTransform to the children.
The idea here is that I would use the container center as the rotation origin and just have the children rotate around that point. That didn't work - in fact, it seems any values supplied as centerX/centerY produce the same behavior, which makes me think that the container might be doing some coercing of the child during layout.
Am I making this way too hard?
This second requirement is tricky because it essentially eliminates the easy solutions -- i.e. Ellipses/Rects inside the container instead of RadDiagramShapes.
Right now, my idea is to hook the OnRotationAngleChanged method in the RadDiagramContainer and then iterate through the children and apply a RotateTransform to the children.
protected
override
void
OnRotationAngleChanged(
double
newValue,
double
oldValue)
{
base
.OnRotationAngleChanged(newValue, oldValue);
List<RadDiagramShape> childrenList = Children.OfType<RadDiagramShape>().ToList();
// get rotation center
double
centerX = (ActualWidth / 2);
double
centerY = (ActualHeight / 2);
foreach
(RadDiagramShape item
in
childrenList)
{
RotateTransform transform =
new
RotateTransform(newValue, centerX, centerY);
item.LayoutTransform = transform;
}
}
The idea here is that I would use the container center as the rotation origin and just have the children rotate around that point. That didn't work - in fact, it seems any values supplied as centerX/centerY produce the same behavior, which makes me think that the container might be doing some coercing of the child during layout.
Am I making this way too hard?
9 Answers, 1 is accepted
0

Tim
Top achievements
Rank 1
answered on 29 Oct 2013, 05:11 PM
I realized that what I'm trying to do is very similar to what is happening to connectors during the rotation process. I peeked at that code and modified my own code to this:
This gets me *very* close to what I need, but it appears the rotation center is off by just a bit. Should I use Bounds instead of ActualBounds here?
protected
override
void
OnRotationAngleChanged(
double
newValue,
double
oldValue)
{
base
.OnRotationAngleChanged(newValue, oldValue);
List<RadDiagramShape> childrenList = Children.OfType<RadDiagramShape>().ToList();
// get rotation center
double
centerX = ActualBounds.Center().X;
double
centerY = ActualBounds.Center().Y;
RotateTransform transform =
new
RotateTransform(newValue - oldValue, centerX, centerY);
foreach
(RadDiagramShape item
in
childrenList)
{
item.Position = transform.Transform(item.Position);
item.RotationAngle = newValue;
}
}
This gets me *very* close to what I need, but it appears the rotation center is off by just a bit. Should I use Bounds instead of ActualBounds here?
0

Tim
Top achievements
Rank 1
answered on 29 Oct 2013, 05:36 PM
This seems to work perfectly...
I have no idea why the -8 offset is needed... maybe BorderThickness or Margin/Padding values?
protected
override
void
OnRotationAngleChanged(
double
newValue,
double
oldValue)
{
base
.OnRotationAngleChanged(newValue, oldValue);
List<RadDiagramShape> childrenList = Children.OfType<RadDiagramShape>().ToList();
// get rotation center
double
centerX = ActualBounds.Center().X;
double
centerY = ActualBounds.Center().Y;
double
offset = -8;
RotateTransform transform =
new
RotateTransform(newValue - oldValue, centerX + offset, centerY + offset);
foreach
(RadDiagramShape item
in
childrenList)
{
item.Position = transform.Transform(item.Position);
item.RotationAngle = newValue;
}
}
I have no idea why the -8 offset is needed... maybe BorderThickness or Margin/Padding values?
0
Accepted
Hi Tim,
I'm really not sure why the 8 pixel offset works for you because when I tried it it didn't seem to work very well. By default there's a 10px margin in the container (DiagramConstants.ContainerMargin) and I guess it could interface with the rotation.
This is the code that works for me and if you want you could try it out :
I hope I was able to help you and if you have further questions please feel free to ask.
Regards,
Zarko
Telerik
I'm really not sure why the 8 pixel offset works for you because when I tried it it didn't seem to work very well. By default there's a 10px margin in the container (DiagramConstants.ContainerMargin) and I guess it could interface with the rotation.
This is the code that works for me and if you want you could try it out :
protected
override
void
OnRotationAngleChanged(
double
newValue,
double
oldValue)
{
base
.OnRotationAngleChanged(newValue, oldValue);
List<RadDiagramShape> childrenList = Children.OfType<RadDiagramShape>().ToList();
// get rotation center
var center =
this
.Bounds.Center();
foreach
(RadDiagramShape shape
in
childrenList)
{
var shapeBounds = shape.Bounds;
shape.RotationAngle = newValue;
var newCenter = shapeBounds.Center().Rotate(center, newValue - oldValue);
shape.Position =
new
Point(newCenter.X - (shapeBounds.Width / 2), newCenter.Y - (shapeBounds.Height / 2));
}
}
Regards,
Zarko
Telerik
TRY TELERIK'S NEWEST PRODUCT - EQATEC APPLICATION ANALYTICS for WPF.
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
0

Tim
Top achievements
Rank 1
answered on 05 Nov 2013, 04:35 PM
That works great.
Unfortunately, it leads to another issue. When loading a diagram and setting the saved rotation angle, the container is sized based on its children being rotated, but itself at a rotation angle of 0 degrees. I've tried various ways of delaying the call to set the rotation until after the container has been created, but nothing has worked so far. Is there a way to lock the container size down or to turn off the automatic sizing to children feature?
Edit: Further investigation points to this piece of code being the culprit:
Since the RotationService isn't being called to rotate the container during load (I'm setting the rotation angle manually), the container bounds are being recalculated when the rotation angle is set. I'm trying to replicate the exact code path that happens when the user rotates the container manually so this is a problem.
I can add RotationServce.StartRotate() and RotationService.CompleteRotate() calls to my OnRotationAngleChanged handler which will fix the loading issue, but this breaks when manually rotating the container. I can probably hack some state flags into the rotation angle handler but would really like a cleaner solution...
Edit #2: I guess the obvious solution of overriding the OnChildBoundsChanged method also works. Huzzah.
Unfortunately, it leads to another issue. When loading a diagram and setting the saved rotation angle, the container is sized based on its children being rotated, but itself at a rotation angle of 0 degrees. I've tried various ways of delaying the call to set the rotation until after the container has been created, but nothing has worked so far. Is there a way to lock the container size down or to turn off the automatic sizing to children feature?
Edit: Further investigation points to this piece of code being the culprit:
protected
virtual
void
OnChildBoundsChanged(IDiagramItem diagramItem)
{
if
(((
this
.isLoaded && (
base
.ServiceLocator !=
null
)) && (!
base
.ServiceLocator.DraggingService.IsDragging && !
base
.ServiceLocator.ResizingService.IsResizing)) && ((!
base
.ServiceLocator.RotationService.IsRotating && !
base
.ServiceLocator.ManipulationPointService.IsManipulating) && !
base
.ServiceLocator.UndoRedoService.IsActive))
{
this
.UpdateContainerLayout();
}
}
Since the RotationService isn't being called to rotate the container during load (I'm setting the rotation angle manually), the container bounds are being recalculated when the rotation angle is set. I'm trying to replicate the exact code path that happens when the user rotates the container manually so this is a problem.
I can add RotationServce.StartRotate() and RotationService.CompleteRotate() calls to my OnRotationAngleChanged handler which will fix the loading issue, but this breaks when manually rotating the container. I can probably hack some state flags into the rotation angle handler but would really like a cleaner solution...
Edit #2: I guess the obvious solution of overriding the OnChildBoundsChanged method also works. Huzzah.
0
Hi Tim,
Yes you can override the OnChildBoundsChanged but I'm not sure if this will help you in your specific scenario because the UpdateContainerLayout is also called on loaded so it'll recalculate the size and mess up the layout. With Q3 we released a couple of new virtual methods (CalculateContentBounds, CalculateShapeBounds and etc.) that you can use to customize the container layout.
For you scenario you can do something like this:
And it should fix the problem.
If you have more questions please feel free to ask.
Regards,
Zarko
Telerik
Yes you can override the OnChildBoundsChanged but I'm not sure if this will help you in your specific scenario because the UpdateContainerLayout is also called on loaded so it'll recalculate the size and mess up the layout. With Q3 we released a couple of new virtual methods (CalculateContentBounds, CalculateShapeBounds and etc.) that you can use to customize the container layout.
For you scenario you can do something like this:
private
void
OnLoaded(
object
sender, RoutedEventArgs e)
{
this
.hasLoaded =
true
;
}
protected
override
Rect CalculateContentBounds(Rect newShapeBounds)
{
if
(
this
.hasLoaded)
return
base
.CalculateContentBounds(newShapeBounds);
return
this
.ContentBounds;
}
If you have more questions please feel free to ask.
Regards,
Zarko
Telerik
TRY TELERIK'S NEWEST PRODUCT - EQATEC APPLICATION ANALYTICS for WPF.
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
0

Tim
Top achievements
Rank 1
answered on 08 Nov 2013, 11:30 PM
Hi Zarko,
That helped a lot!
However, I think you meant for the code snippet to look like this:
if(!hasLoaded) will get you the initial bounds calculation which will then be returned via ContentBounds in subsequent calls.
This got me really close, but there are still a few gotcha's.
My diagram data is loaded via an IGraphSource implementation. I set the position and rotation angle of an item before adding it to the graph. Using the above approach results in the initial content bounds calculation being done with the item at the give rotation angle.
This is a problem because I need the initial calculation done when the item is not rotated, and then have the rotation applied after the bounds are calculated.
To get this working I needed to:
Steps 1-4 are straightforward, but Step 5 is a little tricky because of the timing of applying the rotation angle. I ended up using Dispatcher.BeginInvoke with a low priority to ensure the code inside the begin invoke was called after the diagram had drawn the item initially.
I also had an issue where the item would "walk" on the graph because the position where it ended up after being rotated was not where it was initially saved. I solved this issue in essentially the same way as the rotation issue -- applying the saved position coordinates after the initial render.
It looked something like this:
The side effect of doing all this after the initial draw is detectable movement of items on load as everything gets re-positioned. Is there a better way of doing this that will eliminate the item jumpiness on load?
That helped a lot!
However, I think you meant for the code snippet to look like this:
private
void
OnLoaded(
object
sender, RoutedEventArgs e)
{
this
.hasLoaded =
true
;
}
protected
override
Rect CalculateContentBounds(Rect newShapeBounds)
{
if
(!
this
.hasLoaded)
return
base
.CalculateContentBounds(newShapeBounds);
return
this
.ContentBounds;
}
if(!hasLoaded) will get you the initial bounds calculation which will then be returned via ContentBounds in subsequent calls.
This got me really close, but there are still a few gotcha's.
My diagram data is loaded via an IGraphSource implementation. I set the position and rotation angle of an item before adding it to the graph. Using the above approach results in the initial content bounds calculation being done with the item at the give rotation angle.
This is a problem because I need the initial calculation done when the item is not rotated, and then have the rotation applied after the bounds are calculated.
To get this working I needed to:
- Save of the item's rotation angle
- Set the item's rotation angle to zero
- Add the item to the graph
- Have the diagram draw the item
- Apply the rotation angle
Steps 1-4 are straightforward, but Step 5 is a little tricky because of the timing of applying the rotation angle. I ended up using Dispatcher.BeginInvoke with a low priority to ensure the code inside the begin invoke was called after the diagram had drawn the item initially.
I also had an issue where the item would "walk" on the graph because the position where it ended up after being rotated was not where it was initially saved. I solved this issue in essentially the same way as the rotation issue -- applying the saved position coordinates after the initial render.
It looked something like this:
DispatcherHelper.UIDispatcher.BeginInvoke((Action)(() =>
{
myContainer.Position =
new
Point(savedX, savedY);
myContainer.RotationAngle = savedRotationAngle;
}), DispatcherPriority.ApplicationIdle);
The side effect of doing all this after the initial draw is detectable movement of items on load as everything gets re-positioned. Is there a better way of doing this that will eliminate the item jumpiness on load?
0
Hello Tim,
Sorry for the confusion:
is indeed what I meant. If the ContentBounds are calculated before the loaded event you could try to remove this check:
I'm not sure if I understand this "I also had an issue where the item would "walk" on the graph because the position where it ended up after being rotated was not where it was initially saved" correctly because by default when you rotate a shape it doesn't change its position. The shape position might be changed in the OnRotationAngleChanged method but you could fix this with a simple check:
I've attached a sample project that behaves as you want to (as far as I tested) so could you please take a look at it and if you have more questions feel free to ask.
Regards,
Zarko
Telerik
Sorry for the confusion:
if
(!
this
.hasLoaded)
protected
override
Rect CalculateContentBounds(Rect newShapeBounds)
{
return
this
.ContentBounds;
}
protected
override
void
OnRotationAngleChanged(
double
newValue,
double
oldValue)
{
base
.OnRotationAngleChanged(newValue, oldValue);
if
(!
this
.hasLoaded)
return
;
...
}
Regards,
Zarko
Telerik
TRY TELERIK'S NEWEST PRODUCT - EQATEC APPLICATION ANALYTICS for WPF.
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
0

Tim
Top achievements
Rank 1
answered on 13 Nov 2013, 08:21 PM
Hi Zarko,
Your test application was really helpful and cleared up a lot of things on our side. Thanks!
However, there's one minor thing that remains. When a container's height is set to less than 15 pixels (this may happen on width too, but I haven't tested), the ManipulationAdorner will initially be the correct size, but will expand to a height of 15 pixels when the container shape is moved.
You can see this in your test solution by modifying the container style to this:
I dug into the ManipulationAdorner a little bit, but didn't see any obvious coercing of the size. Any ideas on how to fix that?
Your test application was really helpful and cleared up a lot of things on our side. Thanks!
However, there's one minor thing that remains. When a container's height is set to less than 15 pixels (this may happen on width too, but I haven't tested), the ManipulationAdorner will initially be the correct size, but will expand to a height of 15 pixels when the container shape is moved.
You can see this in your test solution by modifying the container style to this:
<Style TargetType=
"telerik:RadDiagramContainerShape"
>
<Setter Property=
"RotationAngle"
Value=
"{Binding RotationAngle, Mode=TwoWay}"
/>
<Setter Property=
"Position"
Value=
"{Binding Position, Mode=TwoWay}"
/>
<Setter Property=
"MinHeight"
Value=
"0"
/>
<Setter Property=
"MinWidth"
Value=
"0"
/>
<Setter Property=
"Height"
Value=
"10"
/>
</Style>
I dug into the ManipulationAdorner a little bit, but didn't see any obvious coercing of the size. Any ideas on how to fix that?
0
Hi Tim,
There's a diagram constant - MinimumAdornerSize which is 15 by default and that's why the adorner is resized on move(the initial height of 10 is wrong in this case and we'll fix it). For your issue all you have to do is the minimum size to 10:
I hope I was able to help you and if you have more questions please feel free to ask.
Regards,
Zarko
Telerik
There's a diagram constant - MinimumAdornerSize which is 15 by default and that's why the adorner is resized on move(the initial height of 10 is wrong in this case and we'll fix it). For your issue all you have to do is the minimum size to 10:
public
MainWindow()
{
DiagramConstants.MinimumAdornerSize = 10;
InitializeComponent();
....
}
Regards,
Zarko
Telerik
TRY TELERIK'S NEWEST PRODUCT - EQATEC APPLICATION ANALYTICS for WPF.
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>
Learn what features your users use (or don't use) in your application. Know your audience. Target it better. Develop wisely.
Sign up for Free application insights >>