开发者

Modifying a property at design-time doesn't update XAML in Expression Blend 4

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);开发者_StackOverflow社区
    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;
        }
    }
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜