gts13
gts13

Reputation: 1094

How to access UI control from code-behind of ResourceDictionary

So I have a ResourceDictionary to define my custom Window style. What I am struggling to do is to access controls from XAML file. The ResourceDictionary looks like this

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">

    <Setter Property="WindowChrome.WindowChrome">
        <Setter.Value>
            <WindowChrome CaptionHeight="30"/>
        </Setter.Value>
    </Setter>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Window}">
                <Grid>
                    <!-- the root window -->
                    <Border BorderThickness="0.3" BorderBrush="{DynamicResource GeneralDarkBlue}">
                        <AdornerDecorator>
                            <ContentPresenter />
                        </AdornerDecorator>
                    </Border>

                    <DockPanel Height="30" Background="{TemplateBinding Background}" VerticalAlignment="Top" LastChildFill="False">

                        <Viewbox x:Name="HamburgerMenu" DockPanel.Dock="Left" WindowChrome.IsHitTestVisibleInChrome="True">
                            <Viewbox.InputBindings>
                                <MouseBinding MouseAction="LeftClick" Command="{Binding SettingsClick}"/>
                            </Viewbox.InputBindings>
                            <Border Width="47" Height="32" Background="Transparent">
                                <Canvas>
                                    <Path x:Name="TopbarIconHamburgerMenu" Margin="14,10" Data="M12.5,19h19.2v1H12.5V19z M12.5,13.7h19.2v1H12.5V13.7z M12.5,8.5h19.2v1H12.5V8.5z" Stretch="UniformToFill" Fill="#FFFFFF"/>
                                </Canvas>
                            </Border>
                        </Viewbox>

                        // the rest of viewboxes for minimize, maximize controls...

                    </DockPanel>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>

</Style>

And let's say I want to access the HamburgerMenu, so I do something like this

 public partial class MyCustomWindowStyle : ResourceDictionary
 {
        public MyCustomWindowStyle()
        {
            InitializeComponent();
        }

        public void DoSomething()
        {
            var window = (Style)Application.Current.Resources["MyCustomWindowStyle"];
            var hm = (Viewbox)window.Resources.FindName("HamburgerMenu");
        }
}

and this returns null in the hm!

Any idea how to do this?

Upvotes: 1

Views: 1447

Answers (1)

Grx70
Grx70

Reputation: 10349

First of all, Style.Resource is a ResourceDictionary, and there are two important things to notice in the ResourceDictionary.FindName method documentation:

Summary section saying:

Not supported by this Dictionary implementation.

and Return Value section saying:

Always returns null.

Second of all, even if you tried to retrieve the ViewBox by key, it would have to be defined as a resource:

<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
    <Style.Resources>
        <ViewBox x:Key="HamburgerMenu" />
    </Style.Resources>
</Style>

And it is not. It is a part of ControlTemplate's visual tree.

Third of all, ControlTemplate does not contain actual elements, but rather a recipe for creating them. So there's no actual ViewBox living inside the ControlTemplate to retrieve. Notice that ControlTemplate.FindName takes an additional parameter specifying an element for which the template was realized.

However, ControlTemplate does have a LoadContent method, which basically loads the visual tree defined by that template, and I think you could use it, and then invoke FindName on the root element. To simplify retrieval of the ControlTemplate let's first make it a resource:

<Style x:Key="MyCustomWindowStyle" TargetType="{x:Type Window}">
    <Style.Resources>
        <ControlTemplate x:Key="Template" TargetType="{x:Type Window}">
            <Grid>
                (...)
                    <ViewBox x:Key="HamburgerMenu" />
                (...)
            </Grid>
        </ControlTemplate>
    </Style.Resources>
    <Setter Property="Template" Value="{StaticResource Template}" />
</Style>

Then this should do the trick for you:

var window = (Style)Application.Current.Resources["MyCustomWindowStyle"];
var template = (ControlTemplate)window.Resources["Template"];
var root = (FrameworkElement)template.LoadContent();
var hm = (ViewBox)root.FindName("HamburgerMenu");

Update

If your goal is to get hold of the ViewBox in an existing window with that template applied, first you need to know how to get hold of that particular window. It could be the Application.Current.MainWindow, otherwise you're highly likely to find it in the Application.Current.Windows collection. You could also implement the singleton pattern for that window, or use other methods like exposing a static property with reference to that window somewhere in your application, or using third-party tools, such as Service Locator in Prism.

Once you have the window in your hand, you only need to use the previously mentioned ControlTemplate.FindName method:

var window = (...);
var hm = (ViewBox)window.Template.FindName(name: "HamburgerMenu", templatedParent: window);

Note that accessing the resource dictionary in which the template was defined is not necessary.

As for why your attempts with previous solution failed - that's because ControlTemplate.LoadContent method yields freshly created element each time it is invoked, and modifying it does not reflect on elements previously created by that template.

Upvotes: 4

Related Questions