2

我一直在为 WPF 开发一个自定义面板,并且遇到了一些设计时代码的问题。将问题归结为,如果我在设计时运行了一些代码,并且该代码修改了某个对象(面板或面板的子项)上的属性,则在设计器中,我看到了适当的更改,但 XAML组成窗口,没有更新。

例子:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;

namespace StackOverflowExample
{
    class MyPanel : Canvas
    {
        public MyPanel()
        {
            this.SizeChanged += new System.Windows.SizeChangedEventHandler(MyPanel_SizeChanged);
        }

        void MyPanel_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e)
        {
            foreach (FrameworkElement child in this.Children)
            {
                if (child != null)
                {
                    double widthDelta = e.NewSize.Width - e.PreviousSize.Width;
                    double newWidth = Math.Max(0.0, child.Width + widthDelta);
                    child.Width = newWidth;
                }
            }
        }
    }
}

如果您创建一个新的 WPF 应用程序并将该类放入其中,则使用此 XAML 创建一个窗口:

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:StackOverflowExample" x:Class="StackOverflowExample.MainWindow"
        Title="MainWindow" Height="350" Width="525">
    <Canvas>

        <local:MyPanel Height="153" Canvas.Left="108" Canvas.Top="43" Width="278">
            <Rectangle Fill="#FFF4F4F5" Height="77" Canvas.Left="43" Stroke="Black" Canvas.Top="37" Width="87"/>
        </local:MyPanel>

    </Canvas>
</Window>

如果您随后更改面板的宽度(通过在设计器中拖动宽度手柄,或在属性编辑器中更改宽度属性),则内部的矩形将在设计图面中正确更新。但是,如果查看 XAML,只有面板的宽度会更新,矩形的宽度保持不变。如果您在更改面板宽度后构建项目,矩形将返回到 XAML 中定义的值:(

所以我花了很多时间在互联网上寻找解决方案,从我收集到的信息来看,这是因为设计师有源(XAML)的概念,视图设计师的实际东西你'正在玩)和实例(对象的内存中实例化,由 ModelItem 类定义)。它是需要修改的实例,以便从我收集的内容中更新 XAML。但是,我没有获得 ModelInstance 的运气。

这篇文章引导我尝试这样的事情:

...
if (child != null)
{
    double widthDelta = e.NewSize.Width - e.PreviousSize.Width;
    double newWidth = Math.Max(0.0, child.Width + widthDelta);

    EditingContext ec = new EditingContext();
    ModelTreeManager mtm = new ModelTreeManager(ec);
    System.Activities.Presentation.Model.ModelItem model = mtm.CreateModelItem(null, child);
    model.Properties["Width"].SetValue(newWidth);
}

但是,这只是设法使 Blend 崩溃...我在调试器中逐步执行此代码,它告诉我崩溃是 nullRefferenceException,但不清楚什么是 null。我相信它是 modelTreeManager 的根属性,但如果是这样,我不知道如何正确设置它。

所以我想要完成的事情似乎很简单。在设计时更改某物的属性并将更改序列化为 XAML ...据说这是通过在设计器上修改项目的 ModelItem 支持者来处理的,但是,我找不到任何有关如何完成此操作的文档.

进一步阅读
我意识到我的示例正在更改布局,还有其他方法可以实现这一点(例如使用布局系统(arrangeOverride 和 measureOverride),但是这种方法并没有给我所需的控制类型。

此外,我打开了装饰器树,创建了一个没有 UI 的自定义装饰器,但挂钩了面板的 onPropertyChanged 事件并修改了孩子的宽度。这实际上有效,因为装饰者可以获取 ModelItem,但它只能以有限的方式工作。使用装饰器路线,该方法在 Cider (visual studio 2010) 中完美运行,但在 Blend 中仅适用于一半。在混合中,如果在属性编辑器中更改面板的宽度,则子项会更新。但是,如果在混合中拖动设计表面上的宽度手柄,则子项不会更新(在 Visual Studio 中,使用设计表面句柄时子项会更新)。

装饰器的代码要复杂得多,但遵循演练:创建设计时装饰器(搜索 MSDN,我只能发布一个链接)修改为适用于两个设计师(使用 MyAssembly.Design.dll 而不是 MyAssembly.VisualStuido。设计.dll)。可能值得注意的是,此中的不透明度滑块在 Cider 中实时更新(因为它被拖动),但仅在 Blend 中更新鼠标释放。如果有人感兴趣,这是我的装饰器的代码。

class MyPanelAdornerProvider : PrimarySelectionAdornerProvider
{
    private ModelItem adornedControlModel;
    private double previousWidth;

    protected override void Activate(Microsoft.Windows.Design.Model.ModelItem item)
    {
        adornedControlModel = item;
        adornedControlModel.PropertyChanged += new System.ComponentModel.PropertyChangedEventHandler(AdornedControlModel_PropertyChanged);
        previousWidth = (double)adornedControlModel.Properties["Width"].ComputedValue;

        base.Activate(item);
    }

    protected override void Deactivate()
    {
        adornedControlModel.PropertyChanged -= new System.ComponentModel.PropertyChangedEventHandler(AdornedControlModel_PropertyChanged);

        base.Deactivate();
    }

    void AdornedControlModel_PropertyChanged( object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName == "Width")
        {
            double widthDelta = ((double)adornedControlModel.Properties["Width"].ComputedValue) - previousWidth;
            ModelItemCollection children = adornedControlModel.Properties["Children"].Collection;

            foreach (ModelItem item in children)
            {
                item.Properties["Width"].SetValue(Math.Max(0.0, ((double)item.Properties["Width"].ComputedValue) + widthDelta));
            }

            previousWidth = (double)adornedControlModel.Properties["Width"].ComputedValue;
        }
    }
}
4

0 回答 0