WPF 设计器和 Silverlight Designer 加载失败疑难解答
适用于 Visual Studio 的 WPF 设计器包括一个用来呈现 XAML 的可视化设计器,该设计器非常复杂而且可以进行扩展。 如果 XAML 文件无法加载到设计器中,您可以执行若干步骤,尝试了解出错的原因。 本主题描述一些可帮助您对 WPF 设计器加载失败进行疑难解答的提示和方法。 本主题中的示例着重介绍 WPF,但大部分问题、技术和解决方案对于 WPF 和 Silverlight 均适用。
提示
本主题中的许多方法同样适用于 Expression Blend。
调试加载失败问题
使用 Visual Studio 调试器在设计时单步执行您的代码。 可以使用 Visual Studio 的第二个实例来调试加载失败问题。 有关更多信息,请参见如何:调试设计器加载失败问题。
疑难解答步骤
下列步骤可帮助您对 WPF 设计器加载失败进行疑难解答。
阅读收到的所有异常消息。
这看起来是显而易见的,但是如果您获得异常,请认真阅读异常消息。 在某些情况下,异常消息可能会帮助您快速诊断问题。 有关更多信息,请参见在 WPF 设计器中调试和解释错误。
确定问题是否出在您的实现上。
生成并运行您的应用程序,确定问题是仅由您的实现引起的,还是由与 WPF 设计器的交互引起的。 如果该应用程序能够生成并运行,则说明设计时错误可能是由您的实现引起的。
确定问题是否为加载错误。
如果设计视图因异常而无法加载,则说明问题可能是加载错误。 如果您的自定义代码是在设计时加载的,那么,在设计时遇到异常或加载失败时,请参见本主题中的为设计时编写代码一节。
检查在设计时加载的代码。
可通过两种方法来编写在设计时也运行的代码。 第一种方法是通过检查类的输入参数来编写防御性代码。 第二种方法是通过调用 GetIsInDesignMode 方法来检查设计模式是否处于活动状态。 有关更多信息,请参见本主题中的为设计时编写代码一节。
检查代码的其他方面。
使用 WPF 设计器时,请查看本主题中的编程提示一节,以获得一些编程提示。 有关如何编写更可靠的代码的技巧,请查看本主题中的编程最佳方案一节。
如果仍有问题,则可以访问 WPF Designer forum on MSDN(MSDN 上的 WPF 设计器论坛),与使用 WPF 设计器的其他开发人员联系。 若要报告潜在问题或提供建议,请访问 Visual Studio and .NET Framework Feedback(Visual Studio 和 .NET Framework 反馈)网站。
为设计时编写代码
请确保您的代码在设计时和运行时均能够运行。 如果您的代码能够在设计时运行,请不要假定 Application.Current 是您的应用程序。 例如,如果您使用的是 Expression Blend,则 Current 是 Expression Blend。 在设计时,MainWindow 不是应用程序的主窗口。 下面是会导致自定义控件在设计时失败的典型操作:
将 Current 强制转换为 Application 的自定义子类。
将 MainWindow 强制转换为 Window 的自定义子类。
针对 Current 或 MainWindow 使用 FindResource 或 FindName 方法。
不检查 Application.Current 或 Application.MainWindow 返回的值是否为 null。 如果 Visual Studio 未创建应用程序对象,则 Application.Current 可能返回 null。
不检查 Assembly.GetEntryAssembly 返回的值是否为 null。 对于 Visual Studio,此方法返回 null。
可通过两种方法来为设计时编写代码。 第一种方法是通过检查类(如值转换器)的输入参数来编写防御性代码。 第二种方法是通过调用 GetIsInDesignMode 方法来检查设计模式是否处于活动状态。 对于 Silverlight,请使用 IsInDesignTool 属性。
对于某些实现来说,必须检查输入参数,因为对于某些输入,设计环境与运行时环境提供的类型不同。
样式选择器和值转换器通常需要使用其中的一种方法才能在设计时正确运行。
值转换器
您的自定义 IValueConverter 实现应当在 Convert 方法的第一个参数中检查 null 和预期类型。 下面的 XAML 演示了一个 Application.Current 绑定,如果值转换器的实现不正确,该绑定将在设计时失败。
<ComboBox.IsEnabled>
<MultiBinding Converter="{StaticResource specialFeaturesConverter}">
<Binding Path="CurrentUser.Rating" Source="{x:Static Application.Current}"/>
<Binding Path="CurrentUser.MemberSince" Source="{x:Static Application.Current}"/>
</MultiBinding>
</ComboBox.IsEnabled>
该绑定将在设计时引发异常,因为 Application.Current 指的是设计器应用程序(而不是您的应用程序)。 为了防止出现异常,值转换器必须检查其输入参数或者检查设计模式。
下面的代码示例演示如何检查某个值转换器中的输入参数,如果两个输入参数能够满足特定的业务逻辑,则这个值转换器将返回 true。
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Check the values array for correct parameters.
// Designers may send null or unexpected values.
if (values == null || values.Length < 2) return false;
if (!(values[0] is int)) return false;
if (!(values[1] is DateTime)) return false;
int rating = (int)values[0];
DateTime date = (DateTime)values[1];
// If the user has a good rating (10+) and has been a member for
// more than a year, special features are available.
if((rating >= 10) &&
(date.Date < (DateTime.Now.Date - new TimeSpan(365, 0, 0, 0))))
{
return true;
}
return false;
}
为设计时编写代码的第二种方法是检查设计模式是否处于活动状态。 下面的代码示例演示的是设计模式检查(而不是上一个示例中演示的参数检查)。
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// Check for design mode.
if ((bool)(DesignerProperties.IsInDesignModeProperty.GetMetadata(typeof(DependencyObject)).DefaultValue))
{
return false;
}
int rating = (int)values[0];
DateTime date = (DateTime)values[1];
// If the user has a good rating (10+) and has been a member for
// more than a year, special features are available.
if((rating >= 10) &&
(date.Date < (DateTime.Now.Date - new TimeSpan(365, 0, 0, 0))))
{
return true;
}
return false;
}
样式选择器
您的自定义样式选择器还必须实现为能够在设计模式下运行。 下面的 XAML 演示了一个自定义的模板选择器,该模板选择器在运行时使用 Application.MainWindow 来确定哪个资源作为 DataTemplate 返回。 在设计时,此资源可能不可用,因此,SelectTemplate 重写将在设计时返回 null。
<local:TaskListDataTemplateSelector x:Key="myDataTemplateSelector"/>
<ListBox Width="400" Margin="10"
ItemsSource="{Binding Source={StaticResource myTodoList}}"
ItemTemplateSelector="{StaticResource myDataTemplateSelector}"
HorizontalContentAlignment="Stretch"
IsSynchronizedWithCurrentItem="True"/>
下面的代码示例演示了样式选择器的实现。
public class TaskListDataTemplateSelector : DataTemplateSelector
{
public override DataTemplate SelectTemplate(
object item,
DependencyObject container)
{
if (item != null && item is Task)
{
Task taskitem = item as Task;
Window window = Application.Current.MainWindow;
// To run in design mode, either test for the correct window class
// or test for design mode.
if (window.GetType() == typeof(MainWindow))
// Or check for design mode:
//if (!DesignerProperties.GetIsInDesignMode(window))
{
if (taskitem.Priority == 1)
return window.FindResource("importantTaskTemplate") as DataTemplate;
else
return window.FindResource("myTaskTemplate") as DataTemplate;
}
}
return null;
}
}
编程提示
下面是在使用 WPF 设计器时的一些编程提示。
如果您希望自定义控件能够在 WPF 设计器中加载,则必须为已定义的任何依赖项属性提供 CLR get 和 set 方法。 有关更多信息,请参见自定义依赖项属性。
不支持使用类型为 ComboBox 的装饰器。
若要使用第三方 Windows 窗体控件,请创建一个 UserControl 类型,该类型的 Controls 集合中包含供应商控件的一个实例。 有关更多信息,请参见演练:在 WPF 应用程序中承载第三方 Windows 窗体控件。
不直接支持 FlowDocument 的设计时。 如果希望针对嵌入式 FlowDocument 运用 WPF 设计器,请首先将 FlowDocument 放入 Frame 控件,然后便可以在 WPF 设计器中使用该对象。
编程最佳方案
下面是有关如何为 WPF 设计器编写更可靠代码的一些编程最佳方案。
请始终将编辑范围包装在 using 语句或 try/finally 块中。 如果引发了异常,则所做的更改将在 Dispose 调用中被放弃。 有关更多信息,请参见 ModelEditingScope。
使用 ModelEditingScope 将控件从一个容器移动到另一个容器。 如果不这样做,则会引发异常。
在 WPF 和 WPF 设计器中,请不要将打算清除的属性值设置为其默认值。 对于 NaN 值(如 Height),请调用 ClearValue 方法,而不要赋予 NaN。
在从某个属性检索值时,请使用该属性的计算值。 这意味着您应当使用 ComputedValue 属性,而不应当使用 ModelItem 的 GetCurrentValue 方法。 GetCurrentValue 方法返回绑定和其他表达式(前提是它们存储在 XAML 中),因此在某些情况下可能会获得强制转换异常。