我一直在为 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;
}
}
}