在设计时修改属性不会更新 Expression Blend 4 中的 XAML

发布于 2024-10-02 08:40:53 字数 4833 浏览 8 评论 0原文

我一直在开发 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)、视图(您正在使用的设计器上的实际内容)和 Instance(对象的内存实例化,由 ModelItem 类定义)。需要修改该实例才能从我收集的内容中更新 XAML。 然而

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

...
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 崩溃...我在调试器中单步执行了这段代码,它告诉我崩溃是 nullReferenceException,但不清楚什么是 null,我相信它是 modelTreeManager 的根属性,但如果是这样,我不知道如何。正确设置。

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

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

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

装饰器的代码要复杂得多,但遵循演练:创建一个设计时装饰器(搜索MSDN,我只能发布一个链接)修改为适用于两个设计器(使用MyAssembly.Design.dll而不是MyAssembly.VisualStuido.Design.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;
        }
    }
}

I've been working on a custom panel for WPF and have run into a problem with some design-time code. To boil the issue down, if I have some code running at design time, and that code modifies a property on some object (either the panel or a child of the panel), in the designer, I see the appropriate change, but the XAML that makes up the window, nothing updates.

example:

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;
                }
            }
        }
    }
}

If you create a new WPF application and drop that class in, then create a window with this 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>

If you then change the width of the panel (either by dragging the width handle in the designer, or changing the width property in the property editor), the rectangle inside will update correctly in the design surface. However, if you look at the XAML, only the width of the panel updates, the width of the rectangle remains unchanged. If you build the project after changing the width of the panel, the rectangle goes back to the value defined in the XAML :(

So I spent a good amount of time scouring the internet for solutions to this, and from what I've gathered, this is because the designers have the notion of the Source (XAML), the the View (the actual thing on the designer you're playing with) and the Instance (the in-memory instantiation of the object, defined by the ModelItem class). It's the Instance that needs to be modified to have the XAML update from what I gather. However, I've had no luck in getting ahold of the ModelInstance.

This Post Led me down the direction to try something like this:

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

However, this just manages to crash Blend... I stepped though this code in the debugger, and it tells me the crash is a nullRefferenceException, but it's unclear what's null. I believe it's the root property of the modelTreeManager, but if so, I have no idea how to set that correctly.

So what I'm trying to accomplish seems simple enough. Change a property of something at design-time and have the change serialize out to XAML... Supposedly this is handled via modifying the ModelItem backer of the item on the designer, however, I can't find any documentation on how to accomplish this.

Further reading
I realize my example is changing layout, and there are other ways to accomplish this (such as using the layout system (arrangeOverride and measureOverride), however this approach does not give me the type of control I need.

Also, I barked up the adorner tree, creating a custom adorner that had no UI, but hooked the panel's onPropertyChanged event and modified the children's width. This ACTUALLY WORKS, since the adorner can get ahold of the ModelItem, but it only works in limited fashion. With the adorner route, the method works PERFECTLY in Cider (visual studio 2010), but only half-works in Blend. In blend, if the width of the panel is changed in the property editor, the children get updated. However, if the width-handle on the design surface is dragged in blend, the children DO NOT get updated (in visual studio, the children do get updated when the design-surface handle is used).

The code for the adorner is much more complicated, but follows Walkthrough: Creating a Design-time Adorner (search MSDN, I can only post one link) modified to work for both designers (using MyAssembly.Design.dll instead of MyAssembly.VisualStuido.Design.dll). It might be worth noting that the opacity slider in this updates in real-time (as it's dragged) in Cider, but only updates on mouse release in Blend. Here is the code for my adorner if anyone's interested.

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;
        }
    }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文