Home > Software engineering >  Wpf - how to create a library of custom control to use and customize in differente application
Wpf - how to create a library of custom control to use and customize in differente application

Time:02-03

I am creating a collection of custom controls in a project MyLibrary.UI. What I want to achieve is to define the some properties in a component that can be customize in every main app that uses MyLibrary.UI.

I wanto to make an example of customizing an Icon in the control FilteredComboBox.

I tried two ways:

  1. I added a DependencyProperty FindImage defined in FilteredComboBox.cs:
 public class FilteredComboBox : ComboBox
    {
...
    #region FindImageProperty

        public static readonly DependencyProperty FindImageProperty = DependencyProperty.Register(
        nameof(FindImage), typeof(BitmapImage),
        typeof(FilteredComboBox),
        new FrameworkPropertyMetadata
        {
            BindsTwoWayByDefault = true,
            DefaultUpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged,
            DefaultValue = new BitmapImage(new Uri("pack://application:,,,/MyLibrary.Icons;component/Icons/Find.png"))

        });

        public BitmapImage FindImage
        {
            get
            {
                return (BitmapImage)GetValue(FindImageProperty);
            }
            set
            {
                SetValue(FindImageProperty, value);
            }
        }

        #endregion FindImage


        static FilteredComboBox()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FilteredComboBox), new FrameworkPropertyMetadata(typeof(FilteredComboBox)));
        }
...
}

and modified the style FilteredComboBoxStyle.xaml as below:

    <ControlTemplate x:Key="FilteredComboBoxTemplate" TargetType="{x:Type local:FilteredComboBox}">
 ...
                                <DockPanel>
                                    <Image Source="{TemplateBinding FindIcon}" Width="25" Height="25" DockPanel.Dock="Left"/>
                                    <TextBox x:Name="PART_SearchTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SearchText, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center" DockPanel.Dock="Left"/>
                                </DockPanel>
 ...
    </ControlTemplate>

    <Style TargetType="{x:Type local:FilteredComboBox}"  x:Key="baseFilteredCBStyle">
...
        <Setter Property="Template" Value="{StaticResource FilteredComboBoxTemplate}"/>
...
    </Style>

    <Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}"/>

Then in added the reference of this control style in Themes/generic.xaml and defined in the resources of App.xaml of my application the following style:

    <Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}">
<Setter Property="FindImage">
<Setter.Value>
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
</Setter.Value>
</Setter>
</Style>

I would expect that this would change the icon with MyCustomApp.Icons, but it still keeps the Icon in MyLibrary.Icons.

  1. Then I tried to use a DynamicResource to set the image, so in FilteredComboBoxStyle.xaml:
<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyLibrary.Icons;component/Icons/Find.png"/>

    <ControlTemplate x:Key="FilteredComboBoxTemplate" TargetType="{x:Type local:FilteredComboBox}">
 ...
                                <DockPanel>
                                    <Image Source="{DynamicResource myImage}" Width="25" Height="25" DockPanel.Dock="Left"/>
                                    <TextBox x:Name="PART_SearchTextBox" Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=SearchText, UpdateSourceTrigger=PropertyChanged}" VerticalContentAlignment="Center" DockPanel.Dock="Left"/>
                                </DockPanel>
 ...
    </ControlTemplate>
    <Style TargetType="{x:Type local:FilteredComboBox}">
...
        <Setter Property="Template" Value="{StaticResource FilteredComboBoxTemplate}"/>
...
    </Style>

Then in the resources of App.xaml:

<BitmapImage x:Key="myImage" Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>

With this approach my application shows the icon from MyCustomApp.Icons.

My questions are:

Why the option 1 is not working? There is something wrong with it?

Is the option 2 the proper way to customize the custom controls to be application-specific?

I made an example with an Image but could be any property of a control.

CodePudding user response:

The Themes/generic.xaml resource dictionary should be located in the project where the custom control is defined.

If you want to set the FindImage property using an implicit style in the consuming application, you should put the Style in App.xaml or in a resource dictionary that is merged into App.xaml:

<Application x:Class="WpfApp1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             ...>
    <Application.Resources>
        <ResourceDictionary>
            <Style TargetType="{x:Type local:FilteredComboBox}" BasedOn="{StaticResource baseFilteredCBStyle}">
                <Setter Property="FindImage">
                    <Setter.Value>
                        <BitmapImage Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png"/>
                    </Setter.Value>
                </Setter>
            </Style>
        </ResourceDictionary>
    </Application.Resources>
</Application>

CodePudding user response:

Your question is very difficult because it is full of typos and compiler errors. It's hard to tell where you did wrong and where you were just sloppy when creating the example. Maybe you should create the question more carefully next time.

Option 1 is not working because you can't reference a resource defined in Generic.xaml from your application. Generic.xaml is meant as a theme dictionary for theme resources, like the default control Style of FileteredComboBox.

This means you can't base a Style defined in App.xaml on an explicitly named resource that is defined in Generic.xaml.

Option 2 is working because there is no such an illegal reference to a resource that is defined in Generic.xaml. The Style in Generic.xaml references a resource (the BitmapImage) using the DynamicResource markup. The lookup behavior of this markup (opposed to StaticResource) occurs at runtime: once the application is running, lookup starts from the location of the referencing element and traverses up the logical tree to visit every ResourceDictionary along the route. Then the XAML engine checks the App.xaml and finally Generic.xaml for the requested resource.
In your particular case the lookup starts directly at App.xaml, where the XAML engine successfully finds the BitmapImage with the matching key.

To fix your issue, you must base the Style defined within the application scope on the target type (which will implicitly translate to the default Style of the styled control):

BasedOn="{StaticResource {x:Type local:FilteredComboBox}}"

App.xaml

<Style TargetType="{x:Type local:FilteredComboBox}"
       BasedOn="{StaticResource {x:Type local:FilteredComboBox}}">
  <Setter Property="FindImage">
    <Setter.Value>
      <BitmapImage x:Key="myImage"
                    Source="pack://application:,,,/MyCustomApp.Icons;component/Icons/Find.png" />
    </Setter.Value>
  </Setter>
</Style>

I have to point out that it is not necessary to base your Style on the default Style explicitly.
The default Style is always loaded first. Any additional Style that targets the same control will be applied commulative i.e it will be "merged" into the default style: unless FrameworkElement.OverridesDefaultStyle is set to true, only the duplicate property setters are overwritten. Otherwise the default Style will be completely ignored (overridden).
In other words, application scope styles are always implicitly based on the control's default Style.

  • Related