Search code examples
c#wpfvisual-studio-2022

WPF custom Control template style in VS2022


I'm trying to learn how to do custom controls in WPF, via a resource dictionary control template. I have setup a class and WPF test app project in Visual Studio 2022 (.net 7 WPF) like so:

enter image description here

Here is the XAML for the control "ButtonWheel"

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks"
                    xmlns:PT="clr-namespace:PackagingTool.Controls">

    <Style TargetType="{x:Type PT:ButtonWheel}" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type PT:ButtonWheel}">
                    <Grid Background="Aqua" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}">
                        <Label Content="Test"/>
                    </Grid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

</ResourceDictionary>

and the C# for the control:

using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;

namespace PackagingTool.Controls
{
    /// <summary>
    /// A control that represents as a single button, but opens up into a ring of buttons
    /// </summary>
    public class ButtonWheel : Button
    {
        static ButtonWheel() 
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(ButtonWheel), new FrameworkPropertyMetadata(typeof(ButtonWheel)));
        }
    }
}

I also have the Generic Theme, which looks to be required based on the videos I've watched on youtube:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:PT="clr-namespace:PackagingTool.Controls">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="pack://application:,,,/PackagingTool;component/Themes/ButtonWheel.xaml" />
    </ResourceDictionary.MergedDictionaries>

</ResourceDictionary>

When I add this custom control to the test app like:

<Window x:Class="DemoApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:PT="clr-namespace:PackagingTool.Controls;assembly=PackagingTool"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <PT:ButtonWheel HorizontalAlignment="Center" VerticalAlignment="Center" Width="35" Height="35"/>
    </Grid>
</Window>

For some reason its just showing a empty box where the control is, rather than the Grid with a Test label in it, like in the control template. It doesn't give any errors either, and project builds fine, and launches the test app, so I'm assuming I've probably missed something simple, being a newbie :), can anyone advise where I have gone wrong?

I've watched youtube videos: https://www.youtube.com/watch?v=KTNEz7DCRfo, and https://www.youtube.com/watch?v=-YJqoZFAGPA&t=589s, and I dont think I've missed anything. Based on those videos I would expect to see the Test label when I add the control.


Solution

  • I think that you are missing a step. Let's imagine resources handling in WPF like a tree, in which every ResourceDictionary is a leaf and it must be linked with the trunk to work. In our case, the trunk is App.xaml file, which provide to share resources to all application files. How could your MainWindow.xaml see your custom template if it's not linked with it? To solve your problem, you have two possible ways: declaring your ResourceDictionary in MainWindow.xaml (if you want to use it just in that file) or do the same thing but in App.xaml file (to use it in all application files)

    Solution 1

    <Window x:Class="DemoApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:PT="clr-namespace:PackagingTool.Controls;assembly=PackagingTool"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    
    <Window.Resources>
        <ResourceDictionary Source="pack://application:,,,/PackagingTool;component/Themes/ButtonWheel.xaml" />
    </Window.Resources> 
    <Grid>
        <PT:ButtonWheel HorizontalAlignment="Center" VerticalAlignment="Center" Width="35" Height="35"/>
    </Grid>
    

    Solution 2

    <Application x:Class="DemoApp.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/PackagingTool;component/Themes/ButtonWheel.xaml" />
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
    

    P.S. I'm not sure about the declaration of the Resource itself, because you could simply declare it with it's relative path, e.g. DemoApp/CustomControls/YourResource.xaml, but if the tutorial you saw says so, follow its instructions.

    P.P.S. Since you sound not very experienced in WPF, I give you a trick, to check if a ResourceDictionary it's correctly linked, put the cursor on it and press F12 key (Visual Studio shortcut to go to definition), if you open the file, then all works.