Reputation: 34499
EDIT: I have found a minimal way to reproduce the problem. Starting a new Windows Phone app template in Visual Studio and adding this to your 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}" />
and this to your code-behind file (in the OnNavigatedTo
event handler):
btn.Style = TileStyle;
reproduces the problem when you navigate to the page.
Original text: I am trying to create a Windows Phone application that is a minimal version of Whack-A-Mole. On my Page for the game, I have two Styles for Buttons written in XAML, which represent a hole being either occupied or not occupied.
<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>
The first style is essentially just a blank, black, 125x125 square; you can only tell that it's there by clicking on it. The second style is BasedOn that one, and it displays a white_circle.png in place of a blank square.
In my code-behind C# file, I have two methods that utilize these styles: one is called when the Page loads and the other is called when a Button is clicked.
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
When the user first navigates to the page, the Buttons are all black and invisible, as they should be. The white circle appears in the space as intended. However, when it disappears from the spot, the circle leaves behind a Button with no Style applied. (See image.)
Upvotes: 0
Views: 193
Reputation: 70652
Thank you for providing the minimal repro case. That makes it very easy to explain what's going on (well, at least in that case…one hopes that your real-world scenario actually is similar, and this does seem likely).
The problem stems from your use of x:Name
instead of x:Key
for your style resources. In this case, it does not do what you think or would hope it does. In particular, while the compiler does create the field named TileStyle
based on the specified x:Name
value for the resource, and while the XAML compiler permits the use of x:Name
in lieu of a proper key specificed by x:Key
, the two don't connect.
The compiler-generated code to retrieve the value for the TileStyle
field uses a mechanism incompatible with the resource dictionary, i.e. it calls the FindName()
method, passing the name you provided. But that method is for finding named objects within the FrameworkElement
's object graph; it won't (and is not intended to) find objects in the resource dictionary.
Basically, the compiler sees x:Name
and happily emits its boilerplate for implementing a field backing an element. It's just blindly following the rule for implementing x:Name
, which turns out not to work for resources.
So what happens when you call FindName()
, passing a name that doesn't exist? It returns null
. So the TileStyle
field gets (or rather keeps, since that's the default) the value of null
.
And what happens when you set a Button
object's Style
property to null
? It just resets everything to the defaults! The Button
initially looks fine (well, in your real-world scenario) because the {StaticResource TileStyle}
syntax is the correct syntax for dealing with resources, and x:Name
is permitted as a substitute for the resource x:Key
attribute.
In your minimal repro example, the most straight-forward fix is to initialize the resources correctly (use of the x:Name
syntax is for a specific Storyboard
-based scenario and is not generally the correct way to do it), and then to explicitly implement your TileStyle
field:
<Page.Resources>
<Style x:Key="TileStyle" TargetType="Button">
<Setter Property="BorderThickness" Value="0,0,0,0" />
</Style>
</Page.Resources>
Then in your code-behind:
partial class MainPage : Page
{
private Style TileStyle;
public MainPage()
{
InitializeComponent();
TileStyle = (Style)this.Resources["TileStyle"];
// etc.
}
}
With that, you should now have non-null values for your style fields, and assigning them to the Style
properties should update the style correctly instead of resetting it.
That said, it's not clear to me that, while the above is clearly the solution for your minimal repro case, it would apply to your real-world scenario. In particular, in your question you claim that assigning the value of CircleStyle
to a Button
's Style
property does work. But assuming the CircleStyle
field is initialized the same as the TileStyle
field, it's not clear why that would work while assigning TileStyle
would not.
I.e. I would expect the CircleStyle
field to be null
also, but it obviously cannot be if assigning it does change the style to what you want. And if it's not null
, then why would TileStyle
be null
? But, I can't answer anything about that without a good, minimal, complete code example that addresses that scenario precisely.
Finally, I will note that there is probably a better way to do this than to hard-code the styles in the code-behind. Unfortunately, I'm not familiar enough with the Phone API to know what that might be.
In WPF, I would use a StyleSelector
or Trigger
to change the object appearance based on some bound property in an underlying model object. But after a fair amount of time experimenting, I found I just do not yet know enough about the Phone API to know how those would work. The Trigger
class appears to either not even be supported, or at least not be permitted in a Style.Triggers
collection, and I wound up falling down a rabbit hole trying to figure out how to do a Grid
-based ItemsControl
in Phone (in WPF it's not hard, but there's a little trick involved in binding to the Grid.Row
and Grid.Column
attached properties that doesn't work in Phone because apparently Phone doesn't support using Binding
in the Value
property of a style's Setter
).
Basically, there's a ton missing from the Silverlight/Phone/WinRT implementation of a XAML-based API, as compared to WPF. My previous experience with WPF isn't helping me much with the above issues because so much of the features I'd rely on in WPF to implement these behaviors just doesn't exist in the other APIs (shame on Microsoft!).
In the meantime, I hope that the above code-behind-based approach does address your specific issue. If it doesn't, then you will need to post a better, but still minimal and complete code example.
Upvotes: 1