Windows Phone: XAML 在 C# 中应用时样式 'resets' 本身
Windows Phone: XAML Style 'resets' itself when applied in C#
编辑:我找到了重现问题的最小方法。在 Visual Studio 中开始一个新的 Windows Phone 应用程序模板并将其添加到您的 MainPage.xaml
:
<Page.Resources>
<Style x:Name="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
</Style>
</Page.Resources>
<Button x:Name="btn" Style="{StaticResource TileStyle}" />
并将此添加到您的代码隐藏文件(在 OnNavigatedTo
事件处理程序中):
btn.Style = TileStyle;
当您导航到页面时重现问题。
原文: 我正在尝试创建一个 Windows Phone 应用程序,它是 Whack-A-Mole 的最小版本。在我的游戏页面上,我有两个用 XAML 编写的按钮样式,表示一个孔被占用或未被占用。
<Page.Resources>
<Style x:Name="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
<Setter Property="Width" Value="125"/>
<Setter Property="Height" Value="125" />
</Style>
<Style x:Name="CircleStyle" TargetType="Button"
BasedOn="{StaticResource TileStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle Fill="{TemplateBinding Background}" />
<Image Source="/Assets/white_circle.png" />
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<!--later on...-->
<Grid>
<Button Style="{StaticResource TileStyle}" Click="Button_Click"/>
<Button Style="{StaticResource TileStyle}" Click="Button_Click"/>
<Button Style="{StaticResource TileStyle}" Click="Button_Click"/>
...
</Grid>
第一种样式本质上只是一个空白的黑色 125x125 正方形;您只能通过单击它来判断它的存在。第二种样式是基于那个,它显示 white_circle.png 代替空白方块。
在我的代码隐藏 C# 文件中,我有两种使用这些样式的方法:一种在页面加载时调用,另一种在单击按钮时调用。
protected async void OnNavigatedTo(NavigationEventArgs e)
while (!hasUserWonGame)
//sleeps for 1-3 secs
//decides where circle should go at by generating random int
b.Style = CircleStyle; //this works fine!
//more logic, waits
if (!hasUserWonRound)
b.Style = TileStyle; //reverts to TileStyle (this does not work!)
private void Button_Click(object sender, RoutedEventArgs e)
//decides if user is clicking on a circle
if (areTilesDisplayingCircle[index - 1])
//user has won the round!
hasUserWonRound = true;
b.Style = TileStyle; //this also does not work!
//more logic, updates score, decides if user has won the entire game
当用户第一次导航到该页面时,按钮都是黑色且不可见的,因为它们应该是这样的。白色圆圈按预期出现在 space 中。然而,当它从现场消失时,圆圈留下一个没有应用样式的按钮。 (见图。)
感谢您提供最小的复现案例。这使得解释正在发生的事情变得非常容易(好吧,至少在那种情况下......希望你的现实世界场景实际上是相似的,这看起来很可能)。
问题源于您对样式资源使用 x:Name
而不是 x:Key
。在这种情况下,它不会按照您的想法或希望它做的那样做。特别是,编译器确实根据资源的指定 x:Name
值创建名为 TileStyle
的字段,而 XAML 编译器允许使用 x:Name
代替x:Key
指定的正确密钥,两者不连接。
编译器生成的检索 TileStyle
字段值的代码使用了一种与资源字典不兼容的机制,即它调用 FindName()
方法,传递您提供的名称。但该方法用于在 FrameworkElement
的对象图中查找命名对象;它不会(也不打算)在资源字典中查找对象。
基本上,编译器看到 x:Name
并愉快地发出其样板文件以实现支持元素的字段。只是盲目遵循规则实现x:Name
,结果对资源不起作用
那么当您调用 FindName()
并传递一个不存在的名称时会发生什么?它returnsnull
。因此 TileStyle
字段获取(或者更确切地说保留,因为这是默认值) null
.
的值
当您将 Button
对象的 Style
属性 设置为 null
时会发生什么?它只是将所有内容重置为默认值! Button
最初看起来不错(好吧,在您的真实场景中),因为 {StaticResource TileStyle}
语法是处理资源的正确语法,并且允许 x:Name
替代资源x:Key
属性。
在您的最小重现示例中,最直接的修复方法是正确初始化资源(x:Name
语法的使用是针对特定的基于 Storyboard
的场景,通常不是正确的方法),然后显式实现您的 TileStyle
字段:
<Page.Resources>
<Style x:Key="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
</Style>
</Page.Resources>
然后在您的代码隐藏中:
partial class MainPage : Page
{
private Style TileStyle;
public MainPage()
{
InitializeComponent();
TileStyle = (Style)this.Resources["TileStyle"];
// etc.
}
}
这样,您现在应该为您的样式字段设置非空值,并将它们分配给 Style
属性应该会正确更新样式而不是重置它。
也就是说,我不清楚,虽然以上显然是您的最小重现案例的解决方案,但它是否适用于您的真实场景。特别是,在您的问题中,您声称将 CircleStyle
的值分配给 Button
的 Style
属性 确实 有效。但是假设 CircleStyle
字段的初始化与 TileStyle
字段相同,目前尚不清楚为什么分配 TileStyle
不会起作用。
即我希望 CircleStyle
字段也为 null
,但如果分配它确实将样式更改为您想要的样式,则显然不可能。如果不是 null
,那么为什么 TileStyle
会是 null
?但是,如果没有一个很好的、最小的、完整的代码示例来精确地解决 那个 场景,我无法回答任何问题。
最后,我会指出,可能有比在代码隐藏中对样式进行硬编码更好的方法。不幸的是,我对 Phone API 不够熟悉,不知道那可能是什么。
在 WPF 中,我会使用 StyleSelector
或 Trigger
来根据基础模型对象中的某些绑定 属性 更改对象外观。但经过相当多的时间试验后,我发现我对 Phone API 的了解还不够,无法知道它们是如何工作的。 Trigger
class 似乎甚至不受支持,或者至少在 Style.Triggers
集合中是不允许的,我最终掉进了一个兔子洞,试图弄清楚该怎么做Phone 中基于 Grid
的 ItemsControl
(在 WPF 中这并不难,但是在绑定到 Grid.Row
和 Grid.Column
附加属性时有一个小技巧在 Phone 中不起作用,因为显然 Phone 不支持在样式 Setter
的 Value
属性 中使用 Binding
。 =60=]
基本上,与 WPF 相比,基于 XAML 的 API 的 Silverlight/Phone/WinRT 实现缺少 ton。我以前使用 WPF 的经验对解决上述问题没有太大帮助,因为我在 WPF 中依赖的很多功能来实现这些行为在其他 API 中并不存在(微软的耻辱!).
与此同时,我希望上述基于代码隐藏的方法确实能解决您的具体问题。如果没有,那么您将需要 post 一个更好但仍然最少且完整的代码示例。
编辑:我找到了重现问题的最小方法。在 Visual Studio 中开始一个新的 Windows Phone 应用程序模板并将其添加到您的 MainPage.xaml
:
<Page.Resources>
<Style x:Name="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
</Style>
</Page.Resources>
<Button x:Name="btn" Style="{StaticResource TileStyle}" />
并将此添加到您的代码隐藏文件(在 OnNavigatedTo
事件处理程序中):
btn.Style = TileStyle;
当您导航到页面时重现问题。
原文: 我正在尝试创建一个 Windows Phone 应用程序,它是 Whack-A-Mole 的最小版本。在我的游戏页面上,我有两个用 XAML 编写的按钮样式,表示一个孔被占用或未被占用。
<Page.Resources>
<Style x:Name="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
<Setter Property="Width" Value="125"/>
<Setter Property="Height" Value="125" />
</Style>
<Style x:Name="CircleStyle" TargetType="Button"
BasedOn="{StaticResource TileStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<Grid>
<Rectangle Fill="{TemplateBinding Background}" />
<Image Source="/Assets/white_circle.png" />
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Page.Resources>
<!--later on...-->
<Grid>
<Button Style="{StaticResource TileStyle}" Click="Button_Click"/>
<Button Style="{StaticResource TileStyle}" Click="Button_Click"/>
<Button Style="{StaticResource TileStyle}" Click="Button_Click"/>
...
</Grid>
第一种样式本质上只是一个空白的黑色 125x125 正方形;您只能通过单击它来判断它的存在。第二种样式是基于那个,它显示 white_circle.png 代替空白方块。
在我的代码隐藏 C# 文件中,我有两种使用这些样式的方法:一种在页面加载时调用,另一种在单击按钮时调用。
protected async void OnNavigatedTo(NavigationEventArgs e)
while (!hasUserWonGame)
//sleeps for 1-3 secs
//decides where circle should go at by generating random int
b.Style = CircleStyle; //this works fine!
//more logic, waits
if (!hasUserWonRound)
b.Style = TileStyle; //reverts to TileStyle (this does not work!)
private void Button_Click(object sender, RoutedEventArgs e)
//decides if user is clicking on a circle
if (areTilesDisplayingCircle[index - 1])
//user has won the round!
hasUserWonRound = true;
b.Style = TileStyle; //this also does not work!
//more logic, updates score, decides if user has won the entire game
当用户第一次导航到该页面时,按钮都是黑色且不可见的,因为它们应该是这样的。白色圆圈按预期出现在 space 中。然而,当它从现场消失时,圆圈留下一个没有应用样式的按钮。 (见图。)
感谢您提供最小的复现案例。这使得解释正在发生的事情变得非常容易(好吧,至少在那种情况下......希望你的现实世界场景实际上是相似的,这看起来很可能)。
问题源于您对样式资源使用 x:Name
而不是 x:Key
。在这种情况下,它不会按照您的想法或希望它做的那样做。特别是,编译器确实根据资源的指定 x:Name
值创建名为 TileStyle
的字段,而 XAML 编译器允许使用 x:Name
代替x:Key
指定的正确密钥,两者不连接。
编译器生成的检索 TileStyle
字段值的代码使用了一种与资源字典不兼容的机制,即它调用 FindName()
方法,传递您提供的名称。但该方法用于在 FrameworkElement
的对象图中查找命名对象;它不会(也不打算)在资源字典中查找对象。
基本上,编译器看到 x:Name
并愉快地发出其样板文件以实现支持元素的字段。只是盲目遵循规则实现x:Name
,结果对资源不起作用
那么当您调用 FindName()
并传递一个不存在的名称时会发生什么?它returnsnull
。因此 TileStyle
字段获取(或者更确切地说保留,因为这是默认值) null
.
当您将 Button
对象的 Style
属性 设置为 null
时会发生什么?它只是将所有内容重置为默认值! Button
最初看起来不错(好吧,在您的真实场景中),因为 {StaticResource TileStyle}
语法是处理资源的正确语法,并且允许 x:Name
替代资源x:Key
属性。
在您的最小重现示例中,最直接的修复方法是正确初始化资源(x:Name
语法的使用是针对特定的基于 Storyboard
的场景,通常不是正确的方法),然后显式实现您的 TileStyle
字段:
<Page.Resources>
<Style x:Key="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
</Style>
</Page.Resources>
然后在您的代码隐藏中:
partial class MainPage : Page
{
private Style TileStyle;
public MainPage()
{
InitializeComponent();
TileStyle = (Style)this.Resources["TileStyle"];
// etc.
}
}
这样,您现在应该为您的样式字段设置非空值,并将它们分配给 Style
属性应该会正确更新样式而不是重置它。
也就是说,我不清楚,虽然以上显然是您的最小重现案例的解决方案,但它是否适用于您的真实场景。特别是,在您的问题中,您声称将 CircleStyle
的值分配给 Button
的 Style
属性 确实 有效。但是假设 CircleStyle
字段的初始化与 TileStyle
字段相同,目前尚不清楚为什么分配 TileStyle
不会起作用。
即我希望 CircleStyle
字段也为 null
,但如果分配它确实将样式更改为您想要的样式,则显然不可能。如果不是 null
,那么为什么 TileStyle
会是 null
?但是,如果没有一个很好的、最小的、完整的代码示例来精确地解决 那个 场景,我无法回答任何问题。
最后,我会指出,可能有比在代码隐藏中对样式进行硬编码更好的方法。不幸的是,我对 Phone API 不够熟悉,不知道那可能是什么。
在 WPF 中,我会使用 StyleSelector
或 Trigger
来根据基础模型对象中的某些绑定 属性 更改对象外观。但经过相当多的时间试验后,我发现我对 Phone API 的了解还不够,无法知道它们是如何工作的。 Trigger
class 似乎甚至不受支持,或者至少在 Style.Triggers
集合中是不允许的,我最终掉进了一个兔子洞,试图弄清楚该怎么做Phone 中基于 Grid
的 ItemsControl
(在 WPF 中这并不难,但是在绑定到 Grid.Row
和 Grid.Column
附加属性时有一个小技巧在 Phone 中不起作用,因为显然 Phone 不支持在样式 Setter
的 Value
属性 中使用 Binding
。 =60=]
基本上,与 WPF 相比,基于 XAML 的 API 的 Silverlight/Phone/WinRT 实现缺少 ton。我以前使用 WPF 的经验对解决上述问题没有太大帮助,因为我在 WPF 中依赖的很多功能来实现这些行为在其他 API 中并不存在(微软的耻辱!).
与此同时,我希望上述基于代码隐藏的方法确实能解决您的具体问题。如果没有,那么您将需要 post 一个更好但仍然最少且完整的代码示例。