I am trying to understand why a Style is not being applied to the Button control in C#. I have found a simple way to reproduce this problem: off a new Blank App Template for Windows Phone in Visual Studio, add the following lines of code to the 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}" />
When the app is run, the Button is borderless (as it should be). However, when you add this line of code to the OnNavigatedTo
event handler in the code-behind file:
btn.Style = TileStyle;
When you run the app, the button is no longer borderless. Why is this?
There are some quirks with resources and the FindName
method in Windows Phones apps. And in this situation the problem is not tied to the OnNavigatedTo
, Loaded
or the other lifecycle events.
Before digging into the problem here a solution that works in all circumstances. It uses the ResourceDictionary
directly.
Workaround
// this will fail is some circumstances
btn.Style = TileStyle;
// this appears to work at any point in the
// lifecycle, (assuming the style is in the Page.Resources element).
btn.Style = this.Resources["TileStyle"] as Style;
Now for some details. Here is a simple repro that I used for testing.
XAML
<Page.Resources>
<!-- FirstButtonStyle is defined
and accessed with the StaticResource
markup extension. -->
<Style x:Name='FirstButtonStyle'
TargetType='Button'>
<Setter Property="BorderThickness"
Value="0,0,0,0" />
</Style>
<!-- SecondButtonStyle is defined,
but not used -->
<Style x:Name="SecondButtonStyle"
TargetType="Button">
<Setter Property="BorderThickness"
Value="0,0,0,0" />
</Style>
</Page.Resources>
<StackPanel x:Name='MainPanel'>
<Button x:Name="btn1"
Style="{StaticResource FirstButtonStyle}"
Content='Hello'
Click='btn_Click' />
<Button x:Name="btn2"
Content='Hello'
Click='btn_Click' />
</StackPanel>
Windows Phone uses the FindName
method, in the InitializeComponent
method, to set a variable for each named element. You can see this code in the auto-generated file (in my case that's the file NullStylePage.g.i.cs located in the obj folder).
There are five named items in my XAML. Here is the auto-generated code in InitializeComponent
.
Code
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public void InitializeComponent()
{
if (_contentLoaded)
return;
_contentLoaded = true;
global::Windows.UI.Xaml.Application.LoadComponent(this,
new global::System.Uri("ms-appx:///NullStylePage.xaml"), global::Windows.UI.Xaml.Controls.Primitives.ComponentResourceLocation.Application);
FirstButtonStyle = (global::Windows.UI.Xaml.Style)this.FindName("FirstButtonStyle");
SecondButtonStyle = (global::Windows.UI.Xaml.Style)this.FindName("SecondButtonStyle");
MainPanel= (global::Windows.UI.Xaml.Controls.StackPanel)this.FindName("MainPanel");
btn1 = (global::Windows.UI.Xaml.Controls.Button)this.FindName("btn1");
btn2 = (global::Windows.UI.Xaml.Controls.Button)this.FindName("btn2");
}
Put a breakpoint on the last line of this method and you can see which items are null. Since the InitializeComponent()
method is marked with the DebuggerNonUserCodeAttribute
you'll need to turn off the Enable Just My Code option in Tools/Options to step through the code in the debugger.
Here's a screenshot of the Watch Window.
As you can see the value of FirstButtonStyle
is null. The quirk is this. When the style is not applied to an element (with the StaticResource
), then the variable is instantiated correctly. When the style is applied to any element, then the variable is null
.
I'm not sure if this is a known bug but the workaround is to use the style from the Page.Resources.
btn.Style = this.Resources["TileStyle"] as Style;