To make my canvas automatically adjust its size so that my ScrollViewer works properly, I've made a class that derives from Canvas which then overrides the MeasureOverride method to make the canvas adjust its size based on the UIElements present on the canvas.
public class DesignerCanvas : Canvas
{
protected override Size MeasureOverride(Size constraint)
{
Size size = new Size();
foreach (UIElement element in Children)
{
double left = Canvas.GetLeft(element);
double top = Canvas.GetTop(element);
left = double.IsNaN(left) ? 0 : left;
top = double.IsNaN(top) ? 0 : top;
element.Measure(constraint);
Size desiredSize = element.DesiredSize;
if (!double.IsNaN(desiredSize.Width) && !double.IsNaN(desiredSize.Height))
{
size.Width = Math.Max(size.Width, left + desiredSize.Width);
size.Height = Math.Max(size.Height, top + desiredSize.Height);
}
}
// add some extra margin
size.Width += 10;
size.Height += 10;
base.InvalidateMeasure();
return size;
}
}
XAML:
<ScrollViewer HorizontalScrollBarVisibility="Visible">
<local:DesignerCanvas x:Name="designerCanvas" AllowDrop="True">
<ItemsControl Name="Shapes">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</local:DesignerCanvas>
</ScrollViewer>
And the scrollviewer works properly whenever I add/remove graphics directly to the DesignerCanvas! except I'm not. I'm taking the MVVM approach so I'm databinding my canvas to the ObservableCollection
private ObservableCollection<Shape> shapes = new ObservableCollection<Shape>();
public ObservableCollection<Shape> Shapes
{
get { return shapes; }
set { shapes = value; }
}
The shapes show up on the canvas whenever the items are added to the observablecollection. However, the canvas doesn't resize itself properly which causes my scrollviewer to not work anymore. This is expected since I am not directly manipulating the DesignerCanvas, so MeasureOverride is never called.
I could call InvalidateMeasure on DesignerCanvas, but that requires my viewmodel to have knowledge about the view. How do I solve this problem without breaking MVVM?
Edit: I'd also like to hear any recommendations if it's actually worth keeping this pattern for problems like this. Lately, I feel like MVVM is giving me more problems than solutions.
Edit2: Ohh.. Ok, this problem was a lot more convoluted than I thought. From Florian Gl's suggestion, I went ahead and created an event in my viewmodel, then handled that event in my view to call designerCanvas.InvalidateMeasure().
This calls my MeasureOverride method in DesignerCanvas. Except it doesn't adjust my canvas size properly, so scrollviewer still doesn't work. I've noticed that I'm not adding shapes directly to the children of the DesignerCanvas but rather, to the observablecollection which is databound to the Canvas. Hence, there are no UIElement to loop through in my foreach statement
protected override Size MeasureOverride(Size constraint)
{
Size size = new Size();
ItemsControl itemsControl = Children.OfType<ItemsControl>().First();
foreach (UIElement element in Children) //No UIElements inside Children
{
double left = Canvas.GetLeft(element);
...
Now I'm really stuck. How can I circumvent this issue?