WPF MenuItem Icon sharing - c#

I want to bind icons to the MenuItem controls where these items are dynamically created. I tried to set the x:Shared attribute to False but always only the last item has icon.
Here is my style for the MenuItems ItemContainerStyle code:
<Window.Resources>
<Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle" x:Shared="False">
<Setter Property="Icon">
<Setter.Value>
<Image Source="{Binding IconSource}" />
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
And the MenuItem definition:
<MenuItem Header="Workspaces" ItemsSource="{Binding WorkspaceItems}" Icon="{StaticResource BranchIcon}" ItemContainerStyle="{StaticResource MenuItemStyle}" />
I have already tried to set this Shared attribute on the Image control but no luck.
Any suggestion?

You are almost there!
First of all: don't be confuse by Template vs Style.
When you are setting Icon property to an Image control, only one copy is created. As a control can have only one parent, it is removed from the previous parent each time it's re-assigned.
That's why you see only one icon.
You have 2 solutions for what you want:
use datatemplate instead, and redefine the whole Template of a MenuItem
use a style with a shared image component (what you tried to achieve)
In your example the only error is that the Shared attribute should be false on the Image resource, not on the whole style. This should work:
<Window.Resources>
<Image x:Key="MenuIconImage" x:Shared="false" Source="{Binding IconSource}"/>
<Style TargetType="{x:Type MenuItem}" x:Key="MenuItemStyle" BasedOn="{StaticResource {x:Type MenuItem}}">
<Setter Property="Icon" Value="{StaticResource MenuIconImage}">
</Setter>
</Style>
</Window.Resources>
Hope it helps.

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.

C# WPF Style TreeViewItem

I made a custom resource dictionary style for a TreeViewItem, but I am having difficulties with it.
<Style x:Key="StageTreeViewItem" TargetType="{x:Type TreeViewItem}" BasedOn="{StaticResource {x:Type TreeViewItem}}">
<Setter Property="Foreground" Value="Gold"/>
<Setter Property="FontFamily" Value="ArialN"/>
<Setter Property="FontSize" Value="20"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TreeViewItem">
<Grid>
<Image Name="PrimaryButtonImage" Source="pack://application:,,,/Images/TreeViewItem/TreeViewItem_Normal.png"/>
<ContentPresenter VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The content/header of the TreeViewItem does not exist. I put "Stage One" as Header of the TreeViewItem, but it doesn't show up. Also, if I add multiple tree view items on another, it does not expand at all.
Another thing:
How can I remove the highlights when I select the tree view item? I want it to be transparent even when I hover over it and even when I click it. I don't want anything to happen, but I just don't know how, I tried everything.
Your provided code is not making it clear how you're setting header of TreeViewItem.
For other part of the question, you can use Triggers for events happening in WPF forms. Also have a look at this link, as you'll have to define a template for changing background color on mouse hover.
IsMouseOver Trigger not working in WPF

Wpf, style is not being applied

I've written a user control with popup, who's content is being set outside the control. The ControlTemplate of that control looks like the following:
<ControlTemplate TargetType="local:InfoIcon">
<Grid>
<ToggleButton x:Name="HelpButton" Style="{StaticResource HelpButton}" />
<Popup PlacementTarget="{Binding ElementName=HelpButton}" Placement="Bottom"
IsOpen="{Binding ElementName=HelpButton, Path=IsChecked, Mode=TwoWay}" StaysOpen="False">
<Border BorderBrush="#767676" BorderThickness="1"
Background="#f1f2f7">
<Border.Resources>
<!-- Important -->
<Style TargetType="Label">
<Setter Property="Foreground" Value="#575757" />
</Style>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="#575757" />
</Style>
<!-- /Important -->
</Border.Resources>
<ContentPresenter Content="{TemplateBinding HelpContent}" />
</Border>
</Popup>
</Grid>
</ControlTemplate>
The Important part - I want to assign custom styles to items, which are being put inside the popup (it serves as a clickable hint)
I'm using my control in the following way:
<local:MyControl>
<local:MyControl.HelpContent>
<TextBlock>Ala ma kota</TextBlock>
</local:MyControl.HelpContent>
</local:MyControl>
But despite styles in the Border, TextBlock's text's color always inherit the value from its parent (checked using Snoop) - resulting in white text on white background.
You can downlad the small PoC application, which demonstrates the problem.
My observations:
The styling does work for Label. It only doesn't work for TextBlock.
When I add TextBlock.Foreground="Red" to the Border, TextBlock becomes red, still ignoring style (but now using color from Border).
Snoop informs, that this TextBlock actually has the Style resolved correctly. But despite it shouldn't, it uses the inherited value instead of one specified in the style.
How can I solve this problem and why does it occur?
I received answer on Microsoft forums; I'll leave it here in case someone encounters the same problem.
The difference is that a TextBlock is not a control, i.e. it doesn't have any ControlTemplate and because of this the implicit style doesn't get applied to it when it is located inside the StackPanel. Please see the following page for more information: http://blogs.msdn.com/b/wpfsdk/archive/2009/08/27/implicit-styles-templates-controls-and-frameworkelements.aspx
You could use Label elements or set the style for the TextBlock elements explicitly.
-- Magnus (MM8)
Edit2
I've set the Foreground of the UserControl to something else. This behavior is because the child TextBlock controls of the UserControl inherit the Foreground-Settings somehow. This has nothing to do with the popup or some other approaches we tried yet.
I've stumbled upon another question with a similar problems here: Cannot override controls foreground colour in wpf
I suggest to accept this strange behavior and just set a Foreground Color of the UserControl instead:
<Style TargetType="{x:Type local:InfoIcon}">
<Setter Property="Foreground" Value="Red"/>
</Style>
previous Edit
You had my curiousity with this weird behavior, but after looking at your PoC it was rather obvious :) The Popup has some attached Properties TextElement.* where you can style the text elements in the popup. This was new to me, too and I will reseach a bit more afterwards. Nevertheless: Workaround for your Problem is to not style the TextBlock but the Popup instead. your code could look something like following :
<ControlTemplate TargetType="{x:Type local:InfoIcon}">
<ControlTemplate.Resources>
<Style TargetType="Popup">
<Setter Property="TextElement.Foreground" Value="Red"/>
</Style>
<Style TargetType="Label">
<Setter Property="Foreground" Value="Red" />
</Style>
</ControlTemplate.Resources>
<Grid>
<ToggleButton x:Name="TB" Width="16" Height="16"/>
<Popup Placement="Bottom" PlacementTarget="{Binding ElementName=TB}" IsOpen="{Binding ElementName=TB, Path=IsChecked}" StaysOpen="False">
<ContentPresenter Content="{TemplateBinding InfoContent}"/>
</Popup>
</Grid>
</ControlTemplate>
I changed the styles to be outside of the controls, of course you can just use the attached properties of the popup directly. But initially you wanted to know how it works with the styles attached at the border, it does not matter now where you add the styles. You can use a ResourceDictionary for example.
As a suggestion, shouldn't this:
TargetType="local:InfoIcon"
be like this?
TargetType="{x:Type local:InfoIcon}"
Maybe you have some TextBlock style defining that it shouldd take the parent's control foreground.
Did you try to add a BasedOn property like this ?
<Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
<Setter Property="Foreground" Value="#575757" />
</Style>
I tried with your code example and this works :
<ContentPresenter Content="{TemplateBinding InfoContent}">
<ContentPresenter.Style>
<Style TargetType="{x:Type ContentPresenter}">
<Setter Property="TextBlock.Foreground" Value="Red" />
</Style>
</ContentPresenter.Style>
<ContentPresenter.Resources>
<Style TargetType="{x:Type Label}">
<Setter Property="Foreground" Value="Red" />
</Style>
</ContentPresenter.Resources>
</ContentPresenter>
That's kind of odd because when I put the Foreground setter for the Label control inside the ContentPresenter.Style then this time it's Label wich doesn't work...I think it's because Label is a considered as a ContentControl whereas TextBlock is just a FrameworkElement.
Had a similar issue caused by another problem:
There is a strange bug in WPF that prevents styles, defined in merged dictionaries, from being applied to the first element:
https://www.engineeringsolutions.de/wpf-fix-style-is-only-applied-to-first-element/

How to give my custom control an overridable default margin?

Problem
I've created a custom control (OmniBox), which has its base style set with:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="0,2" />
</Style>
But when I'm using my control, I want to be able to do something like:
<UserControl.Resources>
<Style TargetType="{x:Type ui:OmniBox}">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="Margin" Value="0,10"/> <!--Not Working?-->
</Style>
</UserControl.Resources>
<Grid>
<StackPanel>
<ui:OmniBox x:Name="One"... />
<ui:OmniBox x:Name="Two"... />
...
And have all instances of my control take on that default margin. Unfortunately, my controls are not responding to the style set in the resources. They are just keeping their default margin of "0,2".
Strangely, if I explicitly set the margin on my controls like so:
<ui:OmniBox x:Name="One" Margin="0,10" Style="OBDefaultStyle" ... />
<ui:OmniBox x:Name="Two" Margin="0,10" ... />
...
They DO use the margin of "0,10" rather than "0,2". How come the template type isn't working?
If it's relevant, my OmniBox control templates all look like this:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
<ControlTemplate TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultTemplate">
<Grid x:Name="PART_Grid" Style="{StaticResource GridStyle}">
... (Content)
</Grid>
</ControlTemplate>
First Attempt
In my grid style, I've tried setting Margin to
<Setter Property="Margin"
Value="{Binding Path=Margin, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type local:OmniBox}}}" />
But it didn't help in sucking down the templated margin.
Second Attempt
I tried creating a custom margin dependency property and binding the grid to that:
<Style x:Key="GridStyle" TargetType="Grid" BasedOn="{StaticResource BaseElement}">
<Setter Property="Margin" Value="{Binding Path=MyMargin, RelativeSource={RelativeSource TemplatedParent}}" />
</Style>
My custom property was defined as:
public static readonly DependencyProperty MarginProperty = DependencyProperty.Register("Margin", typeof(Thickness), typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2), new PropertyChangedCallback(OnMarginChanged)));
Anyways it didn't work. The default margin set in the dependency property above is still overriding the margin I'm trying to set in the style template.
You can add a default style for a custom control by overriding the metadata for the DefaultStyleKey:
public class MyButton : Button
{
static MyButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MyButton), new FrameworkPropertyMetadata(typeof(MyButton)));
}
}
You then create a resource dictionary called Generic.xaml that is located in a directory called Themes in the root of the project (so the path will be "/Themes/Generic.xaml"). In that resource dictionary you create a default style for your control:
<!-- Base the style on the default style of the base class, if you don't want to completely
replace that style. If you do, remember to specify a new control template in your style as well -->
<Style TargetType="SomeNamespace:MyButton" BasedOn="{StaticResource {x:Type Button}}">
<Setter Property="Margin" Value="10" />
</Style>
If you just add a MyButton control it will get the default style, but you can override properties set in the default style by applying a new style:
<Window x:Class="SomeNamespace.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:SomeNamespace="clr-namespace:SomeNamespace"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style TargetType="SomeNamespace:MyButton">
<Setter Property="Margin" Value="20" />
</Style>
</Window.Resources>
<Grid>
<SomeNamespace:MyButton />
</Grid>
</Window>
GridStyle specifies TargetType="Grid", so the setter <Setter Property="Margin" Value="0,2" /> applies to the Grid at the root of the control template. Setting the Margin property of the containing OmniBox has no effect of the margin of that grid.
Try specifying this in the template:
<Grid x:Name="PART_Grid" Margin="{TemplateBinding Margin}">
Notice I did not set the Style property as you did in the template. This is because the grid's Margin property will always reflect the Margin property of the OmniBox containing it, negating the effect of the Margin property in GridStyle. Instead you will want to default the OmniBox.Margin property and remove GridStyle entirely:
<Style TargetType="{x:Type local:OmniBox}" x:Key="OBDefaultStyle">
<Setter Property="Margin" Value="0 2" />
<Setter Property="Template" Value="{StaticResource OBDefaultTemplate}" />
</Style>
Have you overridden the DefaultStyleKey property in your OmniBox control?
After happening on this question, I figured out what I needed to do. In the control's class, I need to override the margin property's default value:
static OmniBox()
{
MarginProperty.OverrideMetadata(typeof(OmniBox), new FrameworkPropertyMetadata(new Thickness(0,2,0,2)));
}
After that, I get rid of the margin on the "Grid" component of the omnibox completely, since the control itself carries a margin. Now when the user sets the "Margin" property on the OmniBox, it accepts it, if they don't, it uses the default value.
Thank you all so much for your suggestions and effort.

Prevent ItemContainerStyle from overriding already set Style

Is there a way of preventing a ItemContainerStyle from overriding an already set Style (via <Style TargetType="{x:Type MenuItem}">) for instance ?
A style for a MenuItem is already defined within a ResourceDictionary XAML file, which is loaded on App startup :
<ResourceDictionary>
<Style TargetType="{x:Type MenuItem}">
<Setter Property="Foreground" Value="{DynamicResource TextForeground}"/>
.. and so on
</Style>
</ResourceDictionary>
I have the following MenuItem XAML definition. The MenuItem is wrapped inside a ContextMenu of a generic TextBlock (just worth mentioning I guess). All goes well with the menu itself, yet its children (the actual values of the Enum) get a different style, since ItemContainerStyle overrides it :
<MenuItem Header="DisplayType"
Name="DisplayTypeMenu"
ItemsSource="{Binding Source={StaticResource DisplayTypeValues}}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="MenuItem.IsCheckable" Value="True" />
<Style.Triggers>
<Trigger Property="MenuItem.Header"
Value="{x:Static enums:DisplayType.Description}" >
<Setter Property="MenuItem.IsChecked" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
The ItemContainerStyle stems from another question of mine.
The MenuItem is placed within other layers, the top layer being a custom ContentControl :
public class SomeGradientPanel : ContentControl
{
public SomeGradientPanel ()
{
DefaultStyleKey = typeof(SomeGradientPanel );
}
}
The code above seems to be a good candidate for the source of the problem !?
Thus, the complete structure is :
<SomeNameSpace:SomeGradientPanel>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition Height="20"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock x:Name="SomeLabel">
<TextBlock.ContextMenu>
<ContextMenu>
<!-- The MenuItem code snippet from above !-->
</ContextMenu>
</TextBlock.ContextMenu>
</TextBlock>
</Grid>
</SomeNameSpace:SomeGradientPanel>
Can I refer to the already defined Style for the MenuItem within the ItemContainerStyle ? The Style override only occurs on the children of the said MenuItem, the parent has the expected style.
Thank you for your input !
Have you tried
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem" BasedOn="{StaticResource {x:Type MenuItem}}">

Categories

Resources