ContentPresenter steals resources? - c#

In a simple view I want to display two buttons, that have as contents images, that are provided via resources in an extra style.xaml that is loaded in the App.xaml.
<BitmapImage x:Key="AddIcon" UriSource="pack://application:,,,/WpfTestBench;component/Images/plus.png"></BitmapImage>
<BitmapImage x:Key="RemoveIcon" UriSource="pack://application:,,,/WpfTestBench;component/Images/minus.png"></BitmapImage>
<Style x:Key="AddButtonWithIconStyle" TargetType="{x:Type Button}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{DynamicResource AddIcon}" Stretch="None"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
<Setter Property="Content">
<Setter.Value>
<Image Source="{DynamicResource RemoveIcon}" Stretch="None"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Setter.Value>
</Setter>
</Style>
In my view I use the styles like this:
<DockPanel Margin="0,0,5,0" DockPanel.Dock="Bottom" LastChildFill="False" >
<Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"
Style="{StaticResource RemoveButtonWithIconStyle}"/>
<Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0"
Style="{StaticResource AddButtonWithIconStyle}"/>
</DockPanel>
So far this looks good.
But when I add another view into this view via a ContentPresenter, that has basically the same content (again with the above described button styles) the display of the first two buttons (of the parent view) does not work anymore. All I get ist two small circles with the button functionality.
<ContentPresenter Content="{Binding SomeEmbeddedViewModel, UpdateSourceTrigger=PropertyChanged}"/>
Why does this happen? Does the ContentPresenter somehow prevent to share the resources?

As explained in the other answer, you should have the Image control in the ContentTemplate of the Button.
Declare a simple Image Button Style with an Image that has its Source property bound to the actual Content:
<Style x:Key="ImageButtonStyle" TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
...
<Button Style="{StaticResource ImageButtonStyle}"
Content="{DynamicResource RemoveIcon}" .../>
You may also declare the Style as default Button Style, probably inside the DockPanel:
<DockPanel>
<DockPanel.Resources>
<Style TargetType="Button">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{Binding}" Stretch="None"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</DockPanel.Resources>
<Button Command="{Binding DeleteCommand}" DockPanel.Dock="Right"
Content="{DynamicResource RemoveIcon}"/>
<Button Command="{Binding AddCommand}" DockPanel.Dock="Right" Margin="5,0"
Content="{DynamicResource AddIcon}"/>
</DockPanel>

Diagnosis
The core reason of this behavior is that an element can only appear once in the visual tree.
First of all, resources in a ResourceDictionary are by default shared, i.e. every time you fetch a resource with a particular key, you always get the same instance. In your case, you always get the same instance of Style, which also means that there's always only one instance of Image (for each style).
So if you apply the same style to two buttons, the framework tries to put the same Image instance in two different places, which is not allowed. To avoid that, upon attempt to load the image into the visual tree, if it already is in the visual tree, it is unloaded from the previous location first.
That's why the image is only visible in the last button (in order in which they were loaded).
Solution
There are basically two solutions to this problem.
1. Disable resource sharing
You can disable the resource sharing so that each time you fetch a resource from a ResourceDictionary you get a new instance of the resource. In order to do that, you set x:Shared="False" on your resource:
<Style x:Key="AddButtonWithIconStyle" x:Shared="False" TargetType="{x:Type Button}">
(...)
</Style>
2. Use ContentTemplate in your style
Instead of putting the image as the content of the button, you could define a ContentTemplate, which is realized separately for each button it is applied to (so each button gets its own instance of the image):
<Style x:Key="RemoveButtonWithIconStyle" TargetType="{x:Type Button}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<Image Source="{DynamicResource RemoveIcon}" Stretch="None"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
Personally I'd advise you to use the second solution, since that's exactly the purpose of templates in WPF.

Related

DataTemplate Doesn't Apply

I am new to WPF and some help would be appreciated.
I got a resource Dictionary where I am trying to style a ListViewItem or ItemTemplate ListView's Property, But both ways have failed.
First way:
Dictionary
<Style TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<StackPanel Orientation="Horizontal">
<Ellipse Height="45" Width="45" Fill="Gray"/>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
MainWindow
<ListView Grid.Row="1" Background="LightSkyBlue">
<ListViewItem>Text cannot be seen</ListViewItem>
</ListView>
"This one doesn't show any text"
Second Way:
Dictionary
<DataTemplate x:Key="LeftMenuButtons">
<StackPanel Orientation="Horizontal">
<Ellipse Height="45" Width="45" Fill="Gray"/>
<ContentPresenter/>
</StackPanel>
</DataTemplate>
MainWindow
<ListView ItemTemplate="{StaticResource LeftMenuButtons}" Grid.Row="1" >
<ListViewItem>No Effect for DataTemplate Only Text Appears</ListViewItem>
</ListView>
This Doesn't apply anything in DataTemplate.
If possible I would like to know what is going wrong in both two ways ... Thanks in advance.
First Way - Control Template
Please specify the TargetType of the control template explicity.
<ControlTemplate TargetType="{x:Type ListViewItem}">
You could also give the list view item style a name, so it is not applied on all items in your scope, but only to the target list view.
<Style x:Key="ListViewItemStyle"
TargetType="ListViewItem">
Make sure, that you assign the style to your target list view.
<ListView Grid.Row="1"
Background="LightSkyBlue"
ItemContainerStyle="{StaticResource ListViewItemStyle}">
Second Way - Data Template
First, data templates only work if your items are assigned via the ItemsSource property. If you directly assign ListViewItems to your list view, they will not be affected. You have to bind the ItemsSource to a collection. Second, you do not use a ContentPresenter in the data template, you bind to properties of the objects in the ItemsSource collection. In this example you would have a collection of strings that are assigned to the Text property of the TextBlock. Maybe this is beyond the scope of your question.
<ListView Grid.Row="1"
ItemsSource="{Binding StringCollection}"
ItemTemplate="{StaticResource LeftMenuButtons}"/>
<DataTemplate x:Key="LeftMenuButtons"
<StackPanel Orientation="Horizontal">
<Ellipse Height="45"
Width="45"
Fill="Gray"/>
<TextBlock Text="{Binding}"/>
</StackPanel>
</DataTemplate>
The code below works. You just forgot to write ControlTemplate TargetType.
<Style x:Key="ListViewItemStyle" TargetType="ListViewItem">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<StackPanel Orientation="Horizontal">
<Ellipse Height="45" Width="45" Fill="Gray"/>
<ContentPresenter/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

distance between items in listview datatemplate wpf

hi i created a datatemplate for listview now i have 2 problem first is when mouse focus on button my image hide you can see it on below image.
problem 1 and 2
two is The distance between the items is tall and I want their distance being just one line (see below image)
see this image
and this is my datatemplate:
<DataTemplate>
<Border Background="#f0f4f7">
<StackPanel Background="#f5f6fa" Margin="1,1,1,1" VerticalAlignment="Top">
<Border Background="#edf0f5" BorderThickness="5">
<Grid Background="#ffffff" Height="30">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<StackPanel Background="#ffffff" Margin="5" Orientation="Horizontal">
<Button Height="20" Width="20" BorderBrush="Transparent" BorderThickness="0" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.DeleteCommand}">
<Button.Background>
<ImageBrush ImageSource="..\Resources\Delete.png"/>
</Button.Background>
</Button>
<Button Height="20" Width="20" BorderBrush="Transparent" BorderThickness="0" Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type UserControl}}, Path=DataContext.EditCommand}">
<Button.Background>
<ImageBrush ImageSource="..\Resources\Edit.png"/>
</Button.Background>
</Button>
</StackPanel>
<TextBlock Name="txtPhon" Foreground="#7c7f84" HorizontalAlignment="Right" Grid.Column="1" Text="{Binding Path=HomePhoneNumber}"
Margin="0,5,5,5"/>
</Grid>
</Border>
</StackPanel>
</Border>
</DataTemplate>
The problem with mouse making the picture disappear is because of the default template to a button. When you mouseover it gives that blue effect. I think it's in a border which is inside the template so it's above the background.
You can re-template the button to avoid that if you just want your image inside it.
Depending on your exact requirements then you might want slightly different but roughly:
<Button.Template>
<ControlTemplate>
<Border>
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
You could make the whole template just an image.
Particularly if your icons are one colour, I would recommend using a path and define your icons as geometries in a resource dictionary.
Like the letter in this:
https://social.technet.microsoft.com/wiki/contents/articles/32610.wpf-layout-lab.aspx
The gap between your items.
There's padding in the default itemcontainer styling.
You can avoid that by, again, changing the template something like:
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ListViewItem}">
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListView.ItemContainerStyle>
I notice you also have a margin set on your TextBlock, I would temporarily change that and see if that's also part of the problem.
<TextBlock Name="txtPhon" Foreground="#7c7f84" HorizontalAlignment="Right" Grid.Column="1" Text="{Binding Path=HomePhoneNumber}"
**Margin="0,0,5,0"/>**
In order to explore this sort of issue I find Snoop is invaluable. It's free.
Once installed run it after your app.
Drag one of the sight + things over your running window.
Mouse over a suspect piece of UI.
Press Ctrl+Shift and it'll show you all the controls in there and their properties. You can change values in the window and explore what effect the change would have immediately.

Xamarin.Forms: Grouping some Views of the same type

Can I group some Views of the same type so I'll be able to apply changes to all of them at once?
Something like writing in XAML:
<Image GroupName="imagesToScale"/>
<Image/>
<Image GroupName="imagesToScale"/>
<Image GroupName="imagesToScale"/>
And doing in C#:
imagesToScale.Scale = 1.5;
Is it possible? I need to do it one by one?
This is how you do in WPF XAML
<Style TargetType="Image" x:Key="scale">
<Setter Property="LayoutTransform">
<Setter.Value>
<ScaleTransform ScaleX="1.5" ScaleY="1.5"> </ScaleTransform>
</Setter.Value>
</Setter>
</Style>
<Image Style="{StaticResource scale}" ></Image>

WPF: Styling ContentControl

I would like to create a style that could be applied to any ContentControl which would take the ToolTip and add an image to the ContentControl and apply the tool tip text from the object to the image. I have about a hundred of these that need to be done (in various projects) so being able to create a style would save a lot of typing.
What I am trying to recreate is this (ToolTip text is on the blue 'i' and not the 'Reload Employee Data':
which is accomplished via the following:
<StackPanel Orientation="Horizontal">
<CheckBox Content="Reload Employee Data"
IsChecked="{Binding AdjustmentSettings.ReloadEmployeeData}"
Grid.Row="0"
Grid.Column="0">
</CheckBox>
<Image Source="/DelphiaLibrary;Component/Resources/info.ico"
ToolTip="Check if you want to re-upload ..."/>
</StackPanel>
What I am trying to avoid is creating a new stack panel each time I want to add the blue 'i' with the tool tip text on the 'i' and not on the text of the object.
I was able to create the following that works for a Label:
<!-- Works for just Label -->
<Style x:Key="LabelToolTipStyle"
TargetType="{x:Type Label}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Label">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Content}" />
<Image Source="info.ico" ToolTip="{TemplateBinding ToolTip}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And I can call this simply by adding the style to the label like so:
<Label Content="First Text" Style="{StaticResource LabelToolTipStyle}" ToolTip="Label with LabelToolTipStyle" />
I then tried to make this more generalized by creating a style targeting ContentControl but obviously doesn't work because this overrides the entire template (in the case of CheckBox control, the checkbox is missing):
<!-- Works on Label but not CheckBox -->
<Style x:Key="ContentToolTipStyle"
TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ContentControl">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{TemplateBinding Content}" />
<Image Source="info.ico" ToolTip="{TemplateBinding ToolTip}"/>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Is there a way that I could create a style for ContentControls that would allow me to ADD to the template without redefining the entire template? If it cannot be done to ContentControl I wouldn't be opposed to creating a separate style for each control type but would like to avoid redefining the entire template to do so.
You are almost there. You need to create a custom control template for a ContentControl:
<Style x:Key="ToolTipWrapper" TargetType="{x:Type ContentControl}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<StackPanel Orientation="Horizontal">
<StackPanel.ToolTip>
<ToolTip Visibility="Hidden" />
</StackPanel.ToolTip>
<ContentPresenter />
<Image Source="info.ico" ToolTip="{TemplateBinding ToolTip}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Then wrap your elements in a ContentControl and apply the style:
<ContentControl Style="{StaticResource ToolTipWrapper}" ToolTip="Hello world">
<CheckBox Content="I am a check box" />
</ContentControl>
What you can't do is to automatically apply the custom style to all "content" controls: you will always need the extra ContentControl wrapped around each element you want to style in this way.

How to keep MVVM pattern with WPF Path object click?

On a WPF app, I got a Canvas object containing several Path object. I need to do things on click on those Path, things that would be in VM. There is no Command on Path, like Button for example. What is the best way to do that?
My solution so far is :
To create an MouseDown handler on the view code behind to catch my Path click
Send a message from here
Register to this message type on targeted VM in order to execute my business code.
It seems a bit overkill to me, what's your opinion? Do you see a more straitforward method?
Thanks a lot !
You can use Button with Path as Template:
<Button Command="{Binding MyCommand}">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Path ... />
</ControlTemplate>
</Button.Template>
</Button>
Just to add complementary infos about my last comment... The right way to position ItemTemplate is to style the ContentPresenter, within ItemsControl.ItemContainerStyle. Below you will find my finally working code :
<UserControl.Resources>
<Style x:Key="PathStyle" TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Path Data="{Binding Data}"
StrokeStartLineCap="Round"
Stretch="Fill"
StrokeEndLineCap="Round"
Stroke="{DynamicResource selectedZoneBorderColor}"
StrokeLineJoin="Round"
Width="{Binding Path=Width}"
Height="{Binding Path=Height}"
Fill="{DynamicResource selectedZoneColor}"
Panel.ZIndex="1"
StrokeThickness="3" />
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</UserControl.Resources>
<ItemsControl Name="zonesContainer" ItemsSource="{Binding Zones}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Grid.Column="1" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Button
Style="{StaticResource ResourceKey=PathStyle}"
Command="{Binding ElementName=zonesContainer,
Path=DataContext.ActivateZone}"
CommandParameter="{Binding Id}"
/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=Left}" />
<Setter Property="Canvas.Top" Value="{Binding Path=Top}" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
All this with corresponding VM exposing a ObservableCollection, and a Zone class exposing all inside props (Width, Height, Top, Left...).
You can detailled answer here

Categories