为什么我的用户控件会导致 Visual Studio 崩溃? [英] Why does my user control crash Visual Studio?
问题描述
我花了一整天试图弄清楚为什么这个用户控件会导致 VS2010(Windows Phone 7.1 开发)崩溃.应用程序运行此控件没有问题,但是当我在 MainPage.xaml 中进入设计模式时 - VS 崩溃.
I've spent all day trying to figure out why this user control crashes VS2010 (Windows Phone 7.1 development). Application runs this control with no problem, but when I go to design mode in MainPage.xaml - VS crash.
<UserControl x:Class="blabla.Controls.Tile"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
FontFamily="{StaticResource PhoneFontFamilyNormal}"
FontSize="{StaticResource PhoneFontSizeNormal}"
Foreground="{StaticResource PhoneForegroundBrush}">
<UserControl.Resources>
<Storyboard x:Name="SwitchSidesAnimation">
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" Storyboard.TargetName="FrontSide">
<EasingDoubleKeyFrame KeyTime="0" Value="0"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="90"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="90"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause1" KeyTime="0:0:6" Value="-90"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause2" KeyTime="0:0:6.2" Value="-90"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause3" KeyTime="0:0:6.4" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause4" KeyTime="0:0:12" Value="0"/>
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Projection).(PlaneProjection.RotationX)" Storyboard.TargetName="BackSide">
<EasingDoubleKeyFrame KeyTime="0" Value="-90"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.2" Value="-90"/>
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause5" KeyTime="0:0:6" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause6" KeyTime="0:0:6.2" Value="90"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause7" KeyTime="0:0:6.4" Value="90"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause8" KeyTime="0:0:12" Value="90"/>
</DoubleAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="FrontSide">
<DiscreteObjectKeyFrame KeyTime="0:0:0.2">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame KeyTime="0:0:0.4">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame x:Name="MoveThisForPause9" KeyTime="0:0:6">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame x:Name="MoveThisForPause10" KeyTime="0:0:6.4">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame x:Name="MoveThisForPause11" KeyTime="0:0:12">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" Storyboard.TargetName="FrontSide">
<EasingDoubleKeyFrame KeyTime="0:0:0.4" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause12" KeyTime="0:0:6" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause13" KeyTime="0:0:6.2" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause14" KeyTime="0:0:6.4" Value="0"/>
<EasingDoubleKeyFrame x:Name="MoveThisForPause15" KeyTime="0:0:12" Value="0"/>
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</UserControl.Resources>
<Grid x:Name="LayoutRoot">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<!-- Front side -->
<Grid x:Name="FrontSide" Grid.Column="0" Grid.Row="0"
Background="{Binding FrontBackground}">
<Image Source="{Binding FrontImage}" />
</Grid>
<!-- /Front side -->
<!-- Back side -->
<Grid x:Name="BackSide" Grid.Column="0" Grid.Row="0"
Background="{Binding BackBackground}">
<Grid.Projection>
<PlaneProjection RotationX="-90" />
</Grid.Projection>
<Image Source="{Binding BackImage}" />
</Grid>
<!-- /Back side -->
</Grid>
</UserControl>
现在是代码.
namespace blabla.Controls
{
public partial class Tile : UserControl
{
/// <summary>
/// Defines if the tile has two sides.
/// </summary>
public bool IsTwoSided
{
get { return (bool)GetValue(IsTwoSidedProperty); }
set
{
SetValue(IsTwoSidedProperty, value);
this.startAnimations();
}
}
/// <summary>
/// Image that will be displayed on front side.
/// </summary>
public BitmapImage FrontImage
{
get { return (BitmapImage)GetValue(FrontImageProperty); }
set { SetValue(FrontImageProperty, value); }
}
/// <summary>
/// Image that ill be displayed on back side.
/// </summary>
public BitmapImage BackImage
{
get { return (BitmapImage)GetValue(BackImageProperty); }
set { SetValue(BackImageProperty, value); }
}
/// <summary>
/// Brush that will be used as background for front side.
/// </summary>
public Brush FrontBackground
{
get { return (Brush)GetValue(FrontBackgroundProperty); }
set { SetValue(FrontBackgroundProperty, value); }
}
/// <summary>
/// Brush that will be used as background for back side.
/// </summary>
public Brush BackBackground
{
get { return (Brush)GetValue(BackBackgroundProperty); }
set { SetValue(BackBackgroundProperty, value); }
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
public static readonly DependencyProperty IsTwoSidedProperty =
DependencyProperty.Register("IsTwoSided", typeof(bool), typeof(Tile), new PropertyMetadata(false));
public static readonly DependencyProperty FrontImageProperty =
DependencyProperty.Register("FrontImage", typeof(BitmapImage), typeof(Tile), new PropertyMetadata(null));
public static readonly DependencyProperty BackImageProperty =
DependencyProperty.Register("BackImage", typeof(BitmapImage), typeof(Tile), new PropertyMetadata(null));
public static readonly DependencyProperty FrontBackgroundProperty =
DependencyProperty.Register("FrontBackground", typeof(Brush), typeof(Tile),
new PropertyMetadata(new SolidColorBrush((Color)Application.Current.Resources["PhoneAccentColor"])));
public static readonly DependencyProperty BackBackgroundProperty =
DependencyProperty.Register("BackBackground", typeof(Brush), typeof(Tile),
new PropertyMetadata(new SolidColorBrush((Color)Application.Current.Resources["PhoneAccentColor"])));
///////////////////////////////////////////////////////////////////////////////////////////////////////
public Tile()
{
InitializeComponent();
this.LayoutRoot.DataContext = this;
}
///////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// Modifies animation frames' KeyTime to adjust time for new timing.
/// <param name="pauseTime">Lenght of the pause.</param>
/// </summary>
private void setPauses(TimeSpan pauseTime)
{
// Sets pauses.
EasingDoubleKeyFrame frameToModify;
for(int i = 0; true; i++)
{
if(this.FindName("MoveThisForPause" + i) != null)
{
frameToModify = (EasingDoubleKeyFrame)this.FindName("MoveThisForPause" + i);
frameToModify.KeyTime = KeyTime.FromTimeSpan(frameToModify.KeyTime.TimeSpan - TimeSpan.FromSeconds(5) + pauseTime);
}
else
{
break;
}
}
}
/// <summary>
/// Starts animations.
/// </summary>
/// <param name="beginTime">Usually delay before first-time animation.</param>
private void startAnimations()
{
// We start animations only if the tile is two sided.
if(this.IsTwoSided)
{
// Stopping previous animation.
this.SwitchSidesAnimation.Stop();
// Sets correct pauses.
this.setPauses(TimeSpan.FromSeconds(new Random().Next(5, 10)));
// Starts animation.
this.SwitchSidesAnimation.BeginTime = TimeSpan.FromSeconds(new Random().Next(2, 15));
this.SwitchSidesAnimation.RepeatBehavior = RepeatBehavior.Forever;
this.SwitchSidesAnimation.Begin();
}
else
{
this.SwitchSidesAnimation.Stop();
}
}
}
}
推荐答案
无可否认,我使用 Silverlight 的非电话版本和 Visual Web Dev Express 而不是完整版的 VS 能够重现此崩溃.
I have been able to reproduce this crash, admittedly using the non-Phone version of Silverlight, and in Visual Web Dev Express as opposed to the full version of VS.
问题最终归结为这两个依赖属性声明中指定的默认值:
The problem ultimately comes down to the default values specified in these two dependency property declarations:
public static readonly DependencyProperty FrontBackgroundProperty =
DependencyProperty.Register("FrontBackground", typeof(Brush), typeof(Tile),
new PropertyMetadata(new SolidColorBrush((Color)Application.Current.Resources["PhoneAccentColor"])));
public static readonly DependencyProperty BackBackgroundProperty =
DependencyProperty.Register("BackBackground", typeof(Brush), typeof(Tile),
new PropertyMetadata(new SolidColorBrush((Color)Application.Current.Resources["PhoneAccentColor"])));
在我用 null
替换默认值后崩溃消失了(使用 Notepad++ 因为 Visual Web Dev Express 崩溃了),删除了项目的 bin
和 obj
文件夹并重新启动 Visual Web Dev Express.当我重新启动 VWDX 时,它抱怨找不到 Tile
类型,但那是因为我删除了 bin
和 obj
文件夹.重建解决了这个问题.
The crash went away after I replaced the default values with null
(using Notepad++ as Visual Web Dev Express was crashing), deleted the project's bin
and obj
folders and restarted Visual Web Dev Express. When I restarted VWDX, it complained that it couldn't find the type Tile
, but that was because I had deleted the bin
and obj
folders. A rebuild sorted that out.
我只能猜测到底是什么问题.在 Tile
类被静态初始化时,Application.Current
可能是 null
, Application.Current.Resources
> 可能是 null
,或者 Application.Current.Resources["PhoneAccentColor"]
可能是 null
(这会导致转换为 Color
失败,因为 Color
是一个 struct
).也许 VS 设计器没有很好地处理类型静态初始化期间抛出的异常?
I can only guess at exactly what the problem is. At the point the Tile
class is being statically initialized, Application.Current
might be null
, Application.Current.Resources
might be null
, or Application.Current.Resources["PhoneAccentColor"]
might be null
(which would cause the cast to Color
to fail, as Color
is a struct
). Perhaps the VS designer doesn't handle very well exceptions thrown during static initialization of types?
顺便说一下,我还想指出另外几个潜在的问题.首先,这是您的 IsTwoSided
属性:
Incidentally, I'd also like to point out another couple of potential problems. Firstly, this is your IsTwoSided
property:
/// <summary>
/// Defines if the tile has two sides.
/// </summary>
public bool IsTwoSided
{
get { return (bool)GetValue(IsTwoSidedProperty); }
set
{
SetValue(IsTwoSidedProperty, value);
this.startAnimations();
}
}
看起来您希望在 IsTwoSided
依赖项属性更改时调用 startAnimations
方法.您在上面编写的代码无法实现这一点.
It looks like you want the startAnimations
method to be called whenever your IsTwoSided
dependency property changes. The code you've written above will not achieve that.
当 Silverlight 更改依赖属性的值时,它不会调用您的属性设置器来执行此操作.如果您希望在依赖项属性的值更改时发生事情,请使用 属性更改回调.
When Silverlight changes the value of a dependency property, it doesn't call your the property setter to do this. If you want things to happen when a dependency property's value changes, use a property-changed callback instead.
其次,在 Tile.xaml
中,您在
中声明 Storyboard
如下:
Secondly, in Tile.xaml
, you declare the Storyboard
in <UserControl.Resources>
as follows:
<Storyboard x:Name="SwitchSidesAnimation">
我建议使用 x:Key
而不是 x:Name
,原因有两个:
I would recommend using x:Key
instead of x:Name
, for two reasons:
资源字典中的所有项目(隐式样式除外)都必须有一个
x:Key
或一个x:Name
来标识它们.VS 支持使用x:Name
代替x:Key
,但它作为 仅旧支持机制.
all items within resource dictionaries (except implicit styles) must have an
x:Key
or anx:Name
to identify them. VS supports usingx:Name
in place ofx:Key
, but that exists as a legacy support mechanism only.
在用户控件 XAML 的元素中使用 x:Name
会导致 VS 在自动-生成了 Tile
类的一部分(在 obj\Debug 中的 Tile.g.cs
中).但是,仅仅因为您可以将 x:Name
粘贴到元素上并不一定意味着您将能够访问生成字段中的相应对象.由于您的 Tile.xaml
中没有名为 SwitchSidesAnimation
的 UIElement(故事板不是 UIElements),SwitchSidesAnimation
字段将始终为 null代码>.
using x:Name
in an element in a user-control XAML causes VS to create a field with that name in InitializeComponent()
in the auto-generated part of your Tile
class (in Tile.g.cs
somewhere within obj\Debug). However, just because you can stick x:Name
on an element doesn't necessarily mean you'll be able to access the corresponding object in the generated field. Because there is no UIElement named SwitchSidesAnimation
in your Tile.xaml
(Storyboards are not UIElements), the SwitchSidesAnimation
field will always be null
.
确实,x 的 MSDN 文档:关键属性(也链接到上面)提到
Indeed, the MSDN documentation for the x:Key attribute (also linked to above) mentions that
使用键值的 FindName 调用不会检索键控资源
A FindName call using a key value will not retrieve a keyed resource
(FindName
是用于按名称查找控件的方法.如果您查看 Tile.g.cs
,您会看到它在那里使用.)
(FindName
is the method used to look up a control by name. If you look in Tile.g.cs
you'll see it used there.)
我建议始终在资源字典中使用 x:Key
,这样您就不会相信可以在代码隐藏中直接访问此 Storyboard.
I'd recommend always using x:Key
within resource dictionaries so you're not led to believe that you can access this Storyboard directly in code-behind.
要在代码隐藏中访问故事板,请使用
To access the storyboard in code-behind, use
this.Resources["SwitchSidesAnimation"] as Storyboard
事实上,如果您添加以下属性,您将不必更改您的 startAnimations
方法:
In fact, if you add the following property, you won't have to change your startAnimations
method:
private Storyboard SwitchSidesAnimation
{
get { return this.Resources["SwitchSidesAnimation"] as Storyboard; }
}
这篇关于为什么我的用户控件会导致 Visual Studio 崩溃?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!