Issue with applying style on WPF UserControl - c#

I have a user-control and I want to use it in some other project. There is no problem when I set some value to its properties directly:
<local:MyUserControl prop1="val1" prop2="val2">
...
</local:MyUserControl>
But I can't apply a style to it. I tried:
<Window ...>
<Window.Resources>
<Style x:Key="MyUserControlStyle" TargetType="{x:Type local:MyUserControl}">
<Setter Property="prop1" Value="val1"/>
<Setter Property="prop2" Value="val2"/>
</Style>
</Window.Resources>
<Grid>
<local:MyUserControl Style="{StaticResource ResourceKey=MyUserControlStyle}">
...
</local:MyUserControl>
</Grid>
</Window>
Where did I wrong? -Thanks

Using dear #Mario Vernari's instructions, I found it out that the problem was due to a bad strategy which I'd used to create my UserControl. I wanted to create a UserControl that be able to hold some other ones. So I had tried this:
<UserControl x:Class="MyNamespace.MyUserControl"
...
Style="{DynamicResource ResourceKey=MyUserControlStyle}">
<UserControl.Resources>
...
<Style x:Key="MyUserControlStyle" TargetType="{x:Type UserControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type UserControl}">
<Border BorderBrush="{Binding Path=DP1}">
...
<ContentPresenter ... Content="{TemplateBinding Content}"/>
...
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
</UserControl>
Where DP1 is a dependency property of type Brush. The UserControl which has been created through this way works if you set its properties (like DP1) directly. Absolutely this is not the true way as #Mario told me:
...When you use an UserControl, it means that you already know its layout, and there is no need to style the control further. You are defining its style twice at the same time thus results a collision...
And he added:
Instead, you should use a CustomControl; Define the default style in the Themes folder (if you own regular Visual Studio, it makes automatically). Afterward, you may override the default style in your own app. In the same way you would do for a normal base class and its derived.
Follow this:
http://www.codeproject.com/KB/WPF/WPFCustomControl.aspx ...
Obviously, in this case we need to derive our lookless control from ContentControl class (instead of Control class). You may take a look at this & this to master the details.
Here, I give thanks to #Mario again. ;)

You are giving Style="{StaticResource ResourceKey=MyUserControlStyle}".
It's just - Style="{StaticResource MyUserControlStyle}".

Related

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

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:
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 FindImage}" 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 I added the reference of this control style in Themes/generic.xaml and I 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.
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.
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>
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.

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

Edit wpf control template but use original styles

Sometimes when I'm editing a copy of the controls original template I don't need to change the original styles and colors, and would like to reference the original ones directly.
For example, I wanted to change the ComboBox template to add some filtering buttons in the drop down, its toggle button refers to a Style that is also copied into the file. I would like to refer to the original style so my XAML isn't overly cluttered.
Edit:
So here is part of the XAML code that is created when you choose to edit a copy.
The ControlTemplate is what I want to change, but I don't need the ComboBoxToggleButton Style, and so for the toggleButton I'd like to set its style to the one the ComboBoxToggleButton Style was copied from. Is there some namespace that they are all stored in, or are they inaccessible?
<Style x:Key="ComboBoxToggleButton" TargetType="{x:Type ToggleButton}">
...
</Style>
<ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
...
<ToggleButton x:Name="toggleButton" ... Style="{StaticResource ResourceKey=ComboBoxToggleButton}"/>
</Grid>
</ControlTemplate>
And approximately what I'd like it to be like
<Window xmlns:baseStyles="{namespace/url to the default wpf styles}">
<ControlTemplate x:Key="ComboBoxTemplate" TargetType="{x:Type ComboBox}">
<Grid x:Name="templateRoot" SnapsToDevicePixels="true">
...
<ToggleButton x:Name="toggleButton" ... Style="{StaticResource ResourceKey=baseStyles:ComboBoxToggleButton}"/>
</Grid>
<ControlTemplate.Triggers>
...
</ControlTemplate.Triggers>
</ControlTemplate>
Right, so Combobox isn't your basic bare templated control. Within it's ControlTemplate is a unique ToggleButton (hence the additional instance-specific Style template for it) which it requires. Once you introduce a new ControlTemplate than that's now all it knows. It CAN NOT reference a Style template inside of the original ControlTemplate since it's not a resource available outside of it. Style and ControlTemplate are different beasts.
You have two options. Either you take that unique ToggleButton Style Template and put it somewhere it can be reached as a StaticResource and ref it on the ToggleButton instance inside your ControlTemplate via the normal <ToggleButton Style="{StaticResource ComboBoxUniqueToggleButtonStyleKeyNameYouGiveIt}" ..../>
(Like if it were in a resource dictionary, except then it's loaded all the time which generally isn't necessary).
Or, you can embed it directly in your ControlTemplate just like they do in the default style/controltemplate for ComboBox.
You can inherit parts of a Style template via BasedOn but you can only have one ControlTemplate at a time.
Hope this helps, and I'll retract my duplicate vote.
To reuse the default WPF style to ComboBox, use:
<Style TargetType="ComboBox">
<!-- Setters in need of change -->
</ Style>
If you want to inherit from a Style you created yourself, you can use:
<Style TargetType="ComboBox" BasedOn="{StaticResource YourExistentStyle}">
<!-- Setters that need to change -->
</ Style>

How to globally change the styling of the dottet lines around all focusble elements in one place?

From here I learned how to change the styling of dotted lines around focused button. I would like to apply the same thing on all focus-able elements of the current WPF application (or if not possible current page) in one place ( not doing separately for focus-able buttons, textboxes etc)
PS. Preferably in code behind
Define this Style in your App.Xaml .
<Application.Resources>
<Style x:Key="StyleFocusDefault" TargetType="{x:Type Control}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Control}">
<Grid>
<Rectangle StrokeThickness="2" Stroke="Black" StrokeDashArray="2"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Application.Resources>
And then you can apply like : < ... FocusVisualStyle="{DynamicResource StyleFocusDefault}" .../>
This will change FocusVisualStyle for all Controls who have FocusVisualStyle property. You can further experiment with this appaorach for various controls.

Override style for RadioButton if it is placed in a Menu

is there any way to override the default style of the radiobuttons, if it is placed in a menu?
but if it is in the window, it should look like ever. but i will not use x:key. it should found this automatically.
I see two solutions:
Create style with x:Key but place it in Menu.Resources - that way it will be only applied to menu items.
ItemsControls (Menu is one) have property called ItemContainerStyleSelector. You can create Your own StyleSelector and set style depending on container type.
it is very easy, if you know how :)
<style TargetType="{x:Type Menu}">
<Setter Property="Template>
<Setter.Value>
<ControlTemplate TargetType="{x:Type Menu}">
<ControlTemplate.Resources>
<Style Targettype="{x:Type Radiobutton}>
</Style>
</ControlTemplate>
<StackPanel IsItemsHost="True" Width="{TemplateBinding Width}" Height= {TemplateBinding Height} />
</Setter.Value>
</Setter>
</style>
you have to control the writing of the keywords, because i write it so and not on Visual Studio.
override the menu standard with a stackpanel, because i had found no other way to set the Resources.
It is easy, but without the answer from Varius, i didn't found this.
You have to do the same for MenuItem.
I post this, because i think, it maybe would help other peoples with the same problem.
i had searched long time and find nothing.

Categories

Resources