简体   繁体   中英

Canvas with Scrollviewer in MVVM application

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?

My Event-Idea:

  1. Create your own event in ViewModel:

     public delegate void MyCollectionChangedEventArgs(); public event MyCollectionChangedEventArgs MyCollectionChanged; 
  2. Call Event in ViewModel:

     if (MyCollectionChanged != null) { MyCollectionChanged(); } 
  3. Let your View know about the event:

      MainWindowViewModel vm = new MainWindowViewModel(); vm.MyCollectionChanged += new MainWindowViewModel.MyCollectionChangedEventArgs(MainWindowViewModel_MyCollectionChanged); 

    or

      MainWindowViewModel vm = (MainWindowViewModel)DataContext; vm.MyCollectionChanged += new MainWindowViewModel.MyCollectionChangedEventArgs(MainWindowViewModel_MyCollectionChanged); 
  4. Call Method:

     void MainWindowViewModel_MyCollectionChanged() { designerCanvas.InvalidateMeasure() } 

EDIT: I dont know if my following suggestion is working in the MeasureOverride-Method, but you could try:

    (MainWindowViewModel) mvm = (MainWindowViewModel)DataContext; 

    foreach (UIElement element in mvm.Shapes)
    { ... }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM