【问题标题】:Canvas with Scrollviewer in MVVM applicationMVVM 应用程序中带有 Scrollviewer 的画布
【发布时间】:2012-09-25 08:43:24
【问题描述】:

为了使我的画布自动调整其大小,以便我的 ScrollViewer 正常工作,我创建了一个派生自 Canvas 的类,然后重写 MeasureOverride 方法以使画布根据画布上存在的 UIElements 调整其大小。

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>

每当我直接向 DesignerCanvas 添加/删除图形时,滚动查看器都能正常工作!除了我不是。我正在采用 MVVM 方法,因此我将我的画布数据绑定到 ObservableCollection

    private ObservableCollection<Shape> shapes = new ObservableCollection<Shape>();
    public ObservableCollection<Shape> Shapes 
    {
        get { return shapes; }
        set { shapes = value; }
    }

只要将项目添加到 observablecollection,形状就会显示在画布上。但是,画布无法正确调整自身大小,导致我的滚动查看器不再工作。这是意料之中的,因为我没有直接操作 DesignerCanvas,因此从不调用 MeasureOverride。

我可以在 DesignerCanvas 上调用 InvalidateMeasure,但这需要我的视图模型了解视图。如何在不破坏 MVVM 的情况下解决此问题?

编辑:如果确实值得为此类问题保留这种模式,我也想听听任何建议。最近,我觉得 MVVM 给我的问题多于解决方案。

Edit2:哦……好吧,这个问题比我想象的要复杂得多。根据 Florian Gl 的建议,我继续在我的视图模型中创建了一个事件,然后在我的视图中处理该事件以调用 DesignerCanvas.InvalidateMeasure()。

这会调用我在 DesignerCanvas 中的 MeasureOverride 方法。除了它没有正确调整我的画布大小,所以滚动查看器仍然不起作用。我注意到我没有将形状直接添加到 DesignerCanvas 的子代,而是添加到数据绑定到 Canvas 的 observablecollection 中。因此,我的 foreach 语句中没有要循环的 UIElement

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

现在我真的被困住了。我该如何规避这个问题?

【问题讨论】:

  • 为什么不在 xaml.cs 中调用 DesignerCanvas.InvalidateMeasure()?这会破坏 MVVM 吗?
  • @FlorianGl 绝对不是,但问题是我怎么知道何时调用该方法?在我看来,我不知道我的 Viewmodel 中的 observablecollection,所以我无法判断何时从集合中添加/删除项目。我能想到的唯一可行的选择是附加一个周期性计时器或其他东西并调用 invalidate 方法,但这是一个非常肮脏的解决方案。
  • 您可以在 ViewModel 中添加一个事件(例如 MyCollectionChanged),并在您的集合发生更改时触发它。在您的 xaml.cs 中,当事件触发时,您调用 DesignerCanvas.InvalidateMeasure()。

标签: c# .net wpf mvvm canvas


【解决方案1】:

我的活动创意:

  1. 在 ViewModel 中创建您自己的事件:

    public delegate void MyCollectionChangedEventArgs();
    public event MyCollectionChangedEventArgs MyCollectionChanged;
    
  2. ViewModel 中的调用事件:

    if (MyCollectionChanged != null)
    {
        MyCollectionChanged();
    }
    
  3. 让您的视图知道该事件:

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

        MainWindowViewModel vm = (MainWindowViewModel)DataContext;
        vm.MyCollectionChanged += new MainWindowViewModel.MyCollectionChangedEventArgs(MainWindowViewModel_MyCollectionChanged);
    
  4. 调用方法:

    void MainWindowViewModel_MyCollectionChanged()
    {
        designerCanvas.InvalidateMeasure()
    }
    

编辑: 我不知道我的以下建议是否适用于 MeasureOverride-Method,但您可以尝试:

    (MainWindowViewModel) mvm = (MainWindowViewModel)DataContext; 

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

【讨论】:

  • GI 感谢您的回答,这是在不破坏 MVVM 的情况下调用 invalidatemeasure 方法的好方法。但是,我刚刚发现我的问题比这更令人费解。请查看我的编辑。
  • GI 哈哈,这真的很简单。我想我必须习惯跳出框框思考。非常感谢
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-06
  • 1970-01-01
  • 2018-03-22
  • 1970-01-01
  • 2013-08-29
  • 1970-01-01
相关资源
最近更新 更多