WPF Resource Dictionary for UI Styling

Hi Everyone,

I’ve been building a bunch of little tools and they each have a .xaml file for their UI. What I want to do now, is create a WPF resource dictionary to be able to unify the styles across all my tools, but I’m running into errors. I have the resource dictionary saved in my main “.extension” folder. Here is a sample of what my UI is doing to try to find that file:

<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Width="200" Height="200" Topmost="True">
    <Window.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="../../../../../myExtension.extension/xamlStyles.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Window.Resources>

Whenever I run this, I get this error:
System.IO.IOException: Cannot locate resource ‘myextension.extension/xamlstyles.xaml’

Not sure if it means something, but for some reason, the error message doesn’t show the correct capitalization in the names.

Thanks for any suggestions!

Hopefully this is helpful (better late than never). I found it was easier to have the resource dictionaries (and any embedded images) resolve within the Python class, rather than as part of the XAML. I put the resource dictionary (“WPF_styles.xaml”) in a subfolder called “Resources” and set up the following as a template that I use for all my WPF windows:

class MyWindow(forms.WPFWindow):
    def __init__ (self):
        self.resolve_wpf_resource()
        pass
        
    def setup(self):
        self.resolve_images()

    def resolve_wpf_resource(self):
        """Function to add WPF resources."""
        dir_path        = os.path.dirname(__file__)
        path_styles     = os.path.join(dir_path, 'resources/WPF_styles.xaml')
        r               = ResourceDictionary()
        r.Source        = Uri(path_styles)
        self.Resources  = r
        return None

    def resolve_images(self):
        """Add assets folder too"""
        dir_path        = os.path.dirname(__file__)
        
        images = [
            {"xaml_name" : "pdso_concept", "relative_path" : "assets/Floor_Area-Data_Silo_Concept.png"},
            {"xaml_name" : "bbb_logo", "relative_path" : "assets/BBB_Logo.jpg"}
        ]

        for image in images:
            path_to_asset =   os.path.join(dir_path, image["relative_path"])
            wpf_img_element = getattr(self, image["xaml_name"])
            self.set_image_source(wpf_img_element, path_to_asset)
        return None

Credit to @ErikFrits since the solution came largely from some code in one of his Github repos: https://github.com/ErikFrits/EF-Tools/blob/30a535067e01ca0a119f0596f20539e3f6eea895/lib/GUI/WPF_Base.py

Hey,
That’s awesome that you found this example useful!
It took too many evening to make it work like this when I was just starting with WPF

Could you provide a full example of this? it would be very usefull! thanks :muscle:

This is pretty much a full sample; it should work on its own when you instantiate the class, like so:

ui = MyWindow()
# Resources are resolved during init, 
# then setup is called automatically by PyRevit after init.
ui.show()

The only caveat is that you have to update the paths/ lists of paths to the relevant subfolders in your project. I called mine “assets” and “resources” but you can name them anything you like.

And finally, in the xaml file, just make sure the images are defined like so (the “x:name” property is what is used to handle the auto-relinking - the original source gets overwritten, only useful if you’re previewing it in Visual Studio designer):

<Image 
    x:Name="pdso_concept" 
    Source="/Floor_Area-Data_Silo_Concept.png"
    Width="auto" 
/>

Ahhh, this is great! The way I had solved this was to create a separate function that does the resources. I would call it before the “ShowDialog()” function. This will save me a step for sure. Thank you!