Search code examples
c#wpfxamlwindows-phone

Windows Phone: Why is this Style not being applied to the Button in C#?


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?


Solution

  • 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.

    enter image description here

    Here's a screenshot of the Watch Window.

    enter image description here

    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;