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 的值分配给 ButtonStyle 属性 确实 有效。但是假设 CircleStyle 字段的初始化与 TileStyle 字段相同,目前尚不清楚为什么分配 TileStyle 不会起作用。

即我希望 CircleStyle 字段也为 null,但如果分配它确实将样式更改为您想要的样式,则显然不可能。如果不是 null,那么为什么 TileStyle 会是 null?但是,如果没有一个很好的、最小的、完整的代码示例来精确地解决 那个 场景,我无法回答任何问题。


最后,我会指出,可能有比在代码隐藏中对样式进行硬编码更好的方法。不幸的是,我对 Phone API 不够熟悉,不知道那可能是什么。

在 WPF 中,我会使用 StyleSelectorTrigger 来根据基础模型对象中的某些绑定 属性 更改对象外观。但经过相当多的时间试验后,我发现我对 Phone API 的了解还不够,无法知道它们是如何工作的。 Trigger class 似乎甚至不受支持,或者至少在 Style.Triggers 集合中是不允许的,我最终掉进了一个兔子洞,试图弄清楚该怎么做Phone 中基于 GridItemsControl(在 WPF 中这并不难,但是在绑定到 Grid.RowGrid.Column 附加属性时有一个小技巧在 Phone 中不起作用,因为显然 Phone 不支持在样式 SetterValue 属性 中使用 Binding。 =60=]

基本上,与 WPF 相比,基于 XAML 的 API 的 Silverlight/Phone/WinRT 实现缺少 ton。我以前使用 WPF 的经验对解决上述问题没有太大帮助,因为我在 WPF 中依赖的很多功能来实现这些行为在其他 API 中并不存在(微软的耻辱!).

与此同时,我希望上述基于代码隐藏的方法确实能解决您的具体问题。如果没有,那么您将需要 post 一个更好但仍然最少且完整的代码示例。