Images in XAML ResourceDictionary disappear on ToolBar when Menu opens - wpf

I have started to move various common Images into a ResourceDictionary and noticed an odd behavior in my WPF application. If the Image is used in a MenuItem and in a Button on a ToolBar, when I open the Menu the image disappears on the Button.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Image x:Key="NewImage"
Source="/SomeApplication;component/Resources/NewDocumentHS.png"
Stretch="None"/>
<!-- ... -->
Relevant XAML from the Window:
<Menu>
<MenuItem Header="_File">
<MenuItem Header="_New"
Command="{Binding NewCommand}"
Icon="{DynamicResource NewImage}" />
<!-- ... -->
<ToolBarTray>
<ToolBar>
<Button Command="{Binding NewCommand}"
Content="{DynamicResource NewImage}" />
I assume this is a caveat of resources in a ResourceDictionary, but I am unable to discover the appropriate fix for this. Behavior occurs with both StaticResource and DynamicResource. It also doesn't appear to be affected by if the ResourceDictionary stands on its own or if it is merged with others. No other resource shares that key either.
Edit: Additionally, adding PresentationOptions:Freeze="True" to the images did not change the situation.

The Image class is a visual, so it can only appear in the visual tree in one location. Therefore, you cannot share it among multiple MenuItems/Buttons/etc.
You can however share the ImageSource (i.e. Image.Source) value.
In WPF, I believe you can use x:Shared="False" to force WPF to create a new instance for each request though.

You cannot use an Image control in multiple places, it can only be appear in the Visual Tree at one place, so if the call to the resource is made the image is being snatched from the previous owner.
Edit: x:Shared="False" Is obviously a better solution than all of my suggestions below, i wonder why such an important property does not show up in the Intellisense -_-
This behaviour is a bit of a pain, i normally use to predefine a IconStyle and the BitmapImages for the Source of the images but create new Images for every MenuItem where i might need it.
You can also create a DataTemplate for your Icon:
Resources:
<Style x:Key="IconImageStyle" TargetType="{x:Type Image}">
<Setter Property="MaxWidth" Value="16"/>
<Setter Property="MaxHeight" Value="16"/>
</Style>
<DataTemplate x:Key="Icon_Close_Template">
<Image Style="{StaticResource IconImageStyle}"
Source="pack://application:,,,/Images/Close.ico"/>
</DataTemplate>
Usage:
<Menu>
<MenuItem Header="File">
<MenuItem Header="Close">
<MenuItem.Icon>
<ContentPresenter ContentTemplate="{StaticResource Icon_Close_Template}"/>
</MenuItem.Icon>
</MenuItem>
<MenuItem Header="Close">
<MenuItem.Icon>
<ContentPresenter ContentTemplate="{StaticResource Icon_Close_Template}"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
Since templates are created via factories this will work, still significantly inflates the XAML though...
To get around this you could for example write a markup extension, this one is very simple and only copies the values of the Source and Style properties, you could also use reflection or other means to create a complete copy:
[MarkupExtensionReturnType(typeof(object))]
public class IconExtension : MarkupExtension
{
private Image icon;
public Image Icon
{
get { return icon; }
set { icon = value; }
}
public IconExtension() { }
public IconExtension(Image icon)
{
Icon = icon;
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
if (Icon == null) throw new ArgumentNullException("Icon");
return new Image() { Source = Icon.Source, Style = Icon.Style };
}
}
Can be used like this:
<Style x:Key="IconImageStyle" TargetType="{x:Type Image}">
<Setter Property="MaxWidth" Value="16"/>
<Setter Property="MaxHeight" Value="16"/>
</Style>
<Image x:Key="Icon_Close" Style="{StaticResource IconImageStyle}" Source="pack://application:,,,/Images/Close.ico"/>
<!-- ... -->
<MenuItem Header="File">
<MenuItem Header="Close" Icon="{m:Icon {StaticResource Icon_Close}}"/>
<MenuItem Header="Close" Icon="{m:Icon {StaticResource Icon_Close}}"/>
</MenuItem>

Related

Why does my Ribbon Menu Button Popup disappear when I apply this Style?

Here is my XAML:
<Ribbon x:Name="ribbonMain" Height="200" ContextMenu="{x:Null}" VerticalAlignment="Top" ShowQuickAccessToolBarOnTop="False" >
<RibbonTab x:Name="ribbonTabMain" Header="Test Tab" ContextMenu="{x:Null}" >
<RibbonGroup x:Name="ribbonGroupMain" Header="Test Group" ContextMenu="{x:Null}">
<RibbonButton x:Name="ribbonButtonMain" Label="Test Button" ContextMenu="{x:Null}" />
</RibbonGroup>
<RibbonGroup x:Name="ribbonGroupMain2" Header="Test Group 2" ContextMenu="{x:Null}">
<RibbonMenuButton ContextMenu="{x:Null}" Name="ribbonMenuButtonMain" Label="Menu Button">
<RibbonMenuItem ContextMenu="{x:Null}" Name="ribbonMenuItemMain" Header="Menu Item"></RibbonMenuItem>
<RibbonMenuItem ContextMenu="{x:Null}" Name="ribbonMenuItemMain2" Header="Menu Item 2"></RibbonMenuItem>
</RibbonMenuButton>
</RibbonGroup>
</RibbonTab>
</Ribbon>
I then run this C# Code to get the Ribbon Menu Button Default Control Template:
string ribbonMenuButtonControlTemplate = XamlWriter.Save(ribbonMenuButtonMain.Template);
After that I set the x:Name and x:Key properties of the Control Template to something and then put that string of XAML in this:
<Style TargetType="RibbonMenuButton"
<Setter Property="Template">
<Setter.Value>
{DefaultControlTemplateHere}
</Setter.Value>
</Setter>
</Style>
Last I put that Style in my <Window.Resources>.
I wanted to alter the Style from there, but then I realized that the popup just wasn't working anymore.
I expected nothing to change. Seems I was mistaken.
Why does this happen?
Note:
I've tried running this code to see if the popup would open:
if (!ribbonMenuButtonMain.IsDropDownOpen)
{
ribbonMenuButtonMain.IsDropDownOpen = true;
}
With no Style applied that code runs fine and the popup opens.
But with the Style I get this exception:
System.InvalidOperationException: 'This Visual is not connected to a
PresentationSource.'
The XamlWriter.Save method has some serialization limitations that are mentioned here. One of them being that;
Common references to objects made by various markup extension formats, such as StaticResource or Binding, will be dereferenced by the serialization process. These were already dereferenced at the time that in-memory objects were created by the application runtime, and the Save logic does not revisit the original XAML to restore such references to the serialized output.
So your generated template is missing a TemplateBinding to the IsOpen property of the Popup:
<Popup ... IsOpen="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=IsDropDownOpen}">
You may extract the default template including any bindings from C:\Windows\Microsoft.NET\Framework64\v4.0.30319\WPF\System.Windows.Controls.Ribbon.dll using a decompiler such as for example dotPeek.

WPF ControlTemplate for nesting

I want to create a Template for my ContextMenu, which I can reuse as a child for some of my ContextMenu entries:
<ControlTemplate x:Name="StatusContextMenu" x:Key="StatusContextMenu">
<MenuItem>
<MenuItem Header="BLA" />
</MenuItem>
</ControlTemplate>
<ContextMenu x:Shared="false" x:Key="SysTrayMenu">
<MenuItem Header="Online" Command="{Binding SetStatusOnlineCommand}" Template="{StaticResource StatusContextMenu}" />
<MenuItem Header="Away" Command="{Binding SetStatusAbwesendCommand}" Template="{StaticResource StatusContextMenu}" />
</ContextMenu>
Using the ControlTemplate my Headers are empty, but at least every MenuItem has the "Bla"-Child ... I want to achieve this structure:
- Online
- Bla
- Away
- Bla
I will replace "Bla" with the last 10 statustext for the specific status. AND a textbox at the top to set a new statustext.
The statustexts will differ for Online and Away.
Everything is declared inside a ResourceDictionary
My Question is:
How to create a template to reuse in XAML several times?!

Custom WPF tooltip

I want to create a WPF tooltip containing a label for the header of the tooltip and then a textblock containing more detailed text. I've created the following style in a resource dictionary:
<Style x:Key="AppToolTip"
TargetType="ToolTip">
<Setter Property="OverridesDefaultStyle" Value="true" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip">
<StackPanel>
<Label Content="{TemplateBinding Content}" FontWeight="Bold" Background="Blue" Foreground="White">
</Label>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow" Width="200">
</TextBlock>
</StackPanel>
</ControlTemplate>
</Setter.Value></Setter>
</Style>
And can successfully apply this style to a button like so and have the tooltip header appear:
<Button.ToolTip>
<ToolTip Style="{DynamicResource PalletToolTip}">
<Binding Source="{x:Static ResStrings.New}"/>
</ToolTip>
</Button.ToolTip>
What i'm stuck on is how can i set the content of the extra descriptive text from the usage above ? I'm already data binding to the Content property when showing the tooltip header.
Anyone who's read Adam Nathan's WPF Unleashed book will recognise that i'm using his example tooltip XAML but in his case, he's used hard coded strings for the content of the label and textblock. I want to create something that's more reusable and hence want to use data binding to achieve the same effect.
I would inherit a HeaderedToolTip class from ToolTip and add a Header property. I would specify the template for that control much as you've done. Then I would be able to use it like so:
<Button>
<Button.ToolTip>
<HeaderedToolTip Header="My Title" Content="My Content"/>
</Button.ToolTip>
</Button>
Or, with bindings:
<Button>
<Button.ToolTip>
<HeaderedToolTip Header="{Binding ToolTipTitle}" Content="{Binding ToolTipText}"/>
</Button.ToolTip>
</Button>
You can use an object or ViewModel that contains all the necessary properties you need in the tooltip.
class MyToolTipViewModel : INotifyPropertyChanged
{
public string Header
{
get{ return mHeader;}
set{ mHeader = value; RaisePropertyChanged("Header"); }
}
public void RaisePropertyChanged(string aProperty)
{
// .. implementation of INotifyPropertyChanged
}
}
then you can set the tolltip directly on an instance of this class.
myButton.ToolTip = new MyToolTipViewModel();
now after that, your tooltip will just show the full qualified name of the ViewModel class.
What you need now is a DataTemplate, which tells WPF how to convert the class into visual object.
<DataTemplate DataType="{x:Type MyToolTipViewModel}">
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
The DataTemplate need to be placed in the resource tree. In the resource section of a higher level object or directly on the application or window resources level.
Hope that helps.

Dynamically binding to ViewModel commands on Window's MenuItem

Working on a WPF application using the MVVM structure.
My Window displays a menu and the current ViewModel. On one of the Menu's MenuItems, I want to list some Commands found in the current ViewModel. The commands listed in the Menu will change depending on the ViewModel.
I got this to work just fine, but the style is messed up - the Command MenuItems are inside another menu box or something. I'll attach a screenshot.
I wrapped the ViewModel's ICommand objects (RelayCommands, in this instance) in CommandViewModel, which expose the Command and the Display string I want on the menu. These CommandViewModels are in a list: CurrentWorkspace.AdditionalOptionsCommands.
Here is the XAML for the Menu. Like I said, it works, it shows the right items and the commands are executed. The display is just incorrect - can anybody tell me why and how to fix it? See the screenshot.
<Menu>
<MenuItem Header="_Additional Options..." ItemsSource="{Binding Path=CurrentWorkspace.AdditionalOptionsCommands}">
<MenuItem.ItemTemplate>
<DataTemplate DataType="{x:Type vm:CommandViewModel}">
<MenuItem Header="{Binding Path=DisplayText}" Command="{Binding Path=Command}"/>
</DataTemplate>
</MenuItem.ItemTemplate>
</MenuItem>
<MenuItem Header="_Testing">
<MenuItem Header="This looks right" />
<MenuItem Header="This looks right" />
</MenuItem>
</Menu>
Current Appearance:
Desired Appearance:
This is because when you specify menu items via ItemsSource each item gets automatically wrapped into a MenuItem object. This way the content defined in the DataTemplate (MenuItem element) gets wrapped into one more MenuItem.
What you need to do instead of defining a DataTemplate is to define a style for the MenuItem where you setup bindings to the view model's properties and use this style as ItemContainerStyle on the parent MenuItem:
<Window.Resources>
<Style x:Key="CommandMenuItemStyle"
TargetType="{x:Type MenuItem}">
<Setter Property="Header"
Value="{Binding Path=DisplayText}"/>
<Setter Property="Command"
Value="{Binding Path=Command}"/>
</Style>
</Window.Resources>
...
<Menu>
<MenuItem Header="_Additional Options..."
ItemsSource="{Binding Path=CurrentWorkspace.AdditionalOptionsCommands}"
ItemContainerStyle="{StaticResource CommandMenuItemStyle}"/>
<MenuItem Header="_Testing">
<MenuItem Header="This looks right" />
<MenuItem Header="This looks right" />
</MenuItem>
</Menu>
See http://drwpf.com/blog/2008/03/25/itemscontrol-i-is-for-item-container/ for an in-depth explanation on how item containers work with ItemsControl controls.

WPF Extending Window

So I have a bunch of WPF Windows in my Windows Application.
I want each window to have a context menu that has one item ( see the code bellow ). However, I don't feel like copying and pasting this code everywhere. I would like to somehow extend the WPF Window class ( and call it PrintableWindow ) and somehow make every window be an extension of the PrintableWindow ....Is this possible ???
<Window.ContextMenu>
<ContextMenu>
<ContextMenu.Items>
<MenuItem Header="Print"
Click="mnuPrint_Click">
</MenuItem>
</ContextMenu.Items>
</ContextMenu>
</Window.ContextMenu>
You shouldn't need to extend window to do accomplish this, just created a global style for all windows.
In your app.xaml file:
<Style TargetType="{x:Type Window}">
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<ContextMenu.Items>
<MenuItem Header="Print" Command="Print" />
</ContextMenu.Items>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
You will have to create a handler for the print command in the window if you want to print from a particular window. Even better, if you are using a view model you can just bind to your command.
Create your own Window class called PrintableWindow which derives from
Window and then modify any other Window instances to derive from your PrintableWindow versus Window. This way all Window instances will be of type PrintableWindow which has your ContextMenu and handler associated with it.

Resources