I'm trying to implement a specification for how children of a canvas behave when the canvas has a scale transform applied to it. Specifically for images, I need to support cases where an image maintains it's width and height when it's parent is scaled. As I investigate both Render and Layout scale transforms, it appears that a child is not automatically aware of transforms applied to ancestors and therefor doesn't obey it's stretch rule.
Here is an example in xaml:
<Canvas Background="#99000000">
<Canvas Background="white" Canvas.Left="100" Canvas.Top="100" Width="400" Height="300">
<UserControl x:Name="_panelGreen" RenderTransformOrigin=".5 .5">
<Canvas Background="#FF9ACD32" Width="100" Height="100">
<TextBlock Text="Green Box"></TextBlock>
</Canvas>
</UserControl>
<UserControl x:Name="_panelRed" RenderTransformOrigin=".5 .5">
<UserControl.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="{Binding Path=Value, ElementName=xScale}" ScaleY="{Binding Path=Value, ElementName=yScale}" />
<RotateTransform Angle="{Binding Path=Value, ElementName=Rotate}"/>
<TranslateTransform X="{Binding Path=Value, ElementName=xTranslate}" Y="{Binding Path=Value, ElementName=yTranslate}"/>
</TransformGroup>
</UserControl.RenderTransform>
<Canvas Background="red" Width="100" Height="100">
<Image Source="Error-50.png" Stretch="None" Width="50" Height="50"></Image>
<TextBlock Text="Red Box"></TextBlock>
</Canvas>
</UserControl>
</Canvas>
<StackPanel Canvas.Left="800">
<Label Content="Rotate"></Label>
<Slider Name="Rotate" Margin="10,10,10,0" Minimum="0" Maximum="359" Width="100" TickFrequency="1" IsSnapToTickEnabled="True"/>
<Label Content="X Position"></Label>
<Slider Name="xTranslate" Margin="10,10,10,0" Minimum="0" Maximum="500" Width="100" TickFrequency="1" IsSnapToTickEnabled="True"/>
<Label Content="Y Position"></Label>
<Slider Name="yTranslate" Margin="10,10,10,0" Minimum="0" Maximum="500" Width="100" TickFrequency="1" IsSnapToTickEnabled="True"/>
<Label Content="X Scale"></Label>
<Slider Name="xScale" Margin="10,10,10,0" Minimum="1" Maximum="2" Width="100" TickFrequency=".1" IsSnapToTickEnabled="True"/>
<Label Content="Y Scale"></Label>
<Slider Name="yScale" Margin="10,10,10,0" Minimum="1" Maximum="2" Width="100" TickFrequency=".1" IsSnapToTickEnabled="True"/>
</StackPanel>
</Canvas>
Regardless of whether the transform is render or layout, the image scales when the parent scales.
I wondering if someone has a suggestion for an approach to keeping the image 50x50 during the parent scale. Should I try applying an inverse scale to the image by subscribing to the parent scale's changed event?
The actual rules that govern this system can be more complex than this because I have to make this work with an n-depth tree. I'm trying here to start with the most basic example.
Place two Canvases inside a Grid, placing them overlapped. Place scaleable children into one canvas, and non-scaleable ones into another. Do transform on just the scaleable canvas.
Related
I've looked at some related answers (Content of a Button Style appears only in one Button instance, and Images only showing in last ListBoxItem), but can't seem to get their answers to work in my example.
My app wpf stack is relatively complex.
I've a UserControl within another window. Within the UserControl, I've a ListBox with nested elements ListBox.ItemTemplate > DataTemplate > Border > Grid > StackPanel
Within the StackPanel is a TextBlock, followed by an Image and a StackPanel.ToolTip
I'm wanting to place an icon over the Image, so I've further obfuscated the image by putting it in a Grid, and adding a ViewBox accessed via a Control Template (as suggested in the above links), so that the ViewBox is centered on the image. Here's the Grid:
<Grid>
<Image RenderOptions.BitmapScalingMode="HighQuality"
Height="{Binding ElementName=_this, Path=ThumbSize.Height}"
>
<gif:ImageBehavior.AnimatedSource>
<MultiBinding Converter="{c:ImageConverter}">
<Binding Path="ThumbLocation" />
<Binding Path="FullName" />
</MultiBinding>
</gif:ImageBehavior.AnimatedSource>
</Image>
<Control Template="{StaticResource PlaySymbol}" Visibility="{Binding PlayVisible}" />
</Grid>
The ViewBox's ControlTemplate is in the UserControl.Resources up at the top
<ControlTemplate x:Key="PlaySymbol" TargetType="{x:Type Control}">
<Viewbox Stretch="Uniform"
RenderTransformOrigin="0.5,0.5"
Opacity="0.75"
x:Shared="False"
>
<Viewbox.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="0.5" ScaleY="0.5"/>
</TransformGroup>
</Viewbox.RenderTransform>
<ContentControl Content="{StaticResource appbar_control_play}" />
</Viewbox>
</ControlTemplate>
The appbar_control_play is in the Resources directory in an Icons.xaml file.
<Canvas x:Key="appbar_control_play" Width="76" Height="76" Clip="F1 M 0,0L 76,0L 76,76L 0,76L 0,0">
<Path Width="20.5832" Height="31.6667" Canvas.Left="30.0833" Canvas.Top="22.1667" Stretch="Fill" Fill="{DynamicResource BlackBrush}" Data="F1 M 30.0833,22.1667L 50.6665,37.6043L 50.6665,38.7918L 30.0833,53.8333L 30.0833,22.1667 Z "/>
</Canvas>
The goal is to only display the icon for 'play' on movies. I've set the PlayVisible to return the proper visibility for movies, and not for other files. Yet, it is only displaying for the last movie. I've heard that this is the case for controls only able to have one parent. I've tried setting x:Shared="False" on the ViewBox, but to no avail.
The app works, but I've recently decided to add movies to the listing and want to display the play icon over their thumbnails, but not the other items. It seems simple on the outset, but I've yet to figure out what is needed.
Any help would be appreciated, otherwise I feel I may have to resort to overlaying the icon on the actual thumbnails of the movies.
It looks like the problem is not related to the Viewbox but the image resource appbar_control_play it references.
There is no need to add the Viewbox via a Control. Just add it directly to the DataTemplate.
Generally prefer a ContentControl over a templated Control if you wish to display content.
The x:Shared attribute is only required on a UIElement that is not part of a template but defined in a ResourceDictionary. For example, when you define the Viewbox in as a resource, you must set the x:Shared attribute to false. Otherwise it is only allowed to appear once in the visual tree.
In case the image resource is an image file, a proper DataTemplate could look as followed:
<DataTemplate>
<StackPanel>
<Grid>
<Image Source="path to image" />
<Image Source="path to overlay icon"
Stretch="UniformToFill"
Width="50"
Height="50" />
</Grid>
</StackPanel>
</DataTemplate>
In case the icon is a XAML resource like a Geometry or a Segoe MDL2 Assets font icon, the DataTemplate should look as followed:
App.xaml
<Application.Resources>
<Viewbox x:Key="PlayIcon" x:Shared="False">
<TextBlock FontFamily="Segoe MDL2 Assets"
Text="" />
</Viewbox>
<Viewbox x:Key="appbar_control_play"
x:Shared="False">
<Path Width="20.5832"
Height="31.6667"
Stretch="Fill"
Fill="{Binding RelativeSource={RelativeSource AncestroType=ContentControl}, Path=For4ground}"
Data="F1 M 30.0833,22.1667L 50.6665,37.6043L 50.6665,38.7918L 30.0833,53.8333L 30.0833,22.1667 Z " />
</Viewbox>
</Application.Resources>
MyControl.xaml
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<Grid>
<Image Source="path to image" />
<ContentControl Content="{StaticResource appbar_control_play}"
Width="50"
Height="50"
Foreground="Pink" />
</Grid>
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Not sure what's happening on your side but the following just works:
<Window x:Class="abc.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=System.Runtime"
mc:Ignorable="d"
Title="Window1">
<Grid>
<ListBox>
<ListBox.ItemTemplate>
<DataTemplate>
<DataTemplate.Resources>
<BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
</DataTemplate.Resources>
<Border Width="64" Height="64" BorderBrush="Red" BorderThickness="1">
<Grid>
<TextBlock Text="{Binding}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
<Rectangle Fill="DeepSkyBlue"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Margin="2"
Width="16"
Height="16"
Visibility="{Binding Converter={StaticResource BooleanToVisibilityConverter}}"
x:Name="Button" />
</Grid>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
<system:Boolean>True</system:Boolean>
<system:Boolean>False</system:Boolean>
<system:Boolean>True</system:Boolean>
<system:Boolean>False</system:Boolean>
<system:Boolean>True</system:Boolean>
<system:Boolean>False</system:Boolean>
</ListBox>
</Grid>
</Window>
I have a code like this
<Viewbox Grid.Row="1">
<controls:Tile Name="tileInvoice" Click="tileInvoice_Click" VerticalAlignment="Stretch" ToolTip="{x:Static resx:omniLang.Invoice}">
<controls:Tile.Background>
<ImageBrush ImageSource="Resources/invoice.png" Stretch="Uniform"/>
</controls:Tile.Background>
<TextBlock Name="headerInvoice" Text="{x:Static resx:omniLang.Invoice}" FontSize="22" Foreground="Black" FontWeight="Bold" VerticalAlignment="Center" Margin="0,100,0,0" />
</controls:Tile>
</Viewbox>
I would love to use solid color for background, still using png image on it. I'm so out of the ideas already. Should I use VisualBrush to achieve this?
Clearly, a Background property can only have one value, so you simply can't set two backgrounds to the same property. However, there is nothing to stop you from putting your control into a container control and setting the Background property of that as well:
<Viewbox Grid.Row="1">
<Grid Background="Red"> <!-- Set your solid colour here -->
<controls:Tile Name="tileInvoice" Click="tileInvoice_Click" VerticalAlignment="Stretch" ToolTip="{x:Static resx:omniLang.Invoice}">
<controls:Tile.Background>
<ImageBrush ImageSource="Resources/invoice.png" Stretch="Uniform"/>
</controls:Tile.Background>
<TextBlock Name="headerInvoice" Text="{x:Static resx:omniLang.Invoice}" FontSize="22" Foreground="Black" FontWeight="Bold" VerticalAlignment="Center" Margin="0,100,0,0" />
</controls:Tile>
</Grid>
</Viewbox>
Can someone give me an example of how to style a button with XAML hat has three states (hover, normal, pressed). i want the whole area of the button to be covered with an image (one for each of the three different states) and i want text to be on top that also has three different colors for the different states. i have something like this already (without the color states on the textblock). the problem i'm having at this point is that the textblock is blocking the input events for the button undernearth (i also haven't implemented the color changes for the textblock....
current code:
<DataTemplate x:Name="SubjectItemTemplate">
<Canvas Width="225" Height="225">
<Button Canvas.Left="0" Canvas.Top="0"
Command="{Binding ElementName=LayoutRoot, Path=DataContext.NavigateToUnitsPage}"
CommandParameter="{Binding}">
<Button.Template>
<ControlTemplate>
<Grid Background="{Binding LightThemeColor}" Width="205" Height="205">
<controls:ImageButton HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,0"
NormalStateImageSource="{Binding ImageUriNormal}"
HoverStateImageSource="{Binding ImageUriHover}"
PressedStateImageSource="{Binding ImageUriPressed}" Command="{Binding ElementName=LayoutRoot, Path=DataContext.NavigateToUnitsPage}"
CommandParameter="{Binding}"/>
<TextBlock Text="{Binding Name}" FontSize="18" TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,168,0,0" />
</Grid>
</ControlTemplate>
</Button.Template>
</Button>
</Canvas>
</DataTemplate>
The reason that the ImageButton events are not being handled as expected with the TextBlock is because the TextBlock is located in-line with the ImageButton and not contained inside the ImageButton. To change this, the TextBlock has to be placed inside the ImageButton.
<controls:ImageButton HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,0,0,0"
NormalStateImageSource="{Binding ImageUriNormal}"
HoverStateImageSource="{Binding ImageUriHover}"
PressedStateImageSource="{Binding ImageUriPressed}" Command="{Binding ElementName=LayoutRoot, Path=DataContext.NavigateToUnitsPage}" CommandParameter="{Binding}" >
<TextBlock Text="{Binding Name}" FontSize="18" TextWrapping="Wrap" TextAlignment="Center" HorizontalAlignment="Center" VerticalAlignment="Top" Margin="0,168,0,0" />
</controls:ImageButton>
I'm working on building a diagnostic reader for my car. I can parse the messages from my OBD port but I want to create a display that is better than just a text readout. I want a graphical display of my car that will highlight the affected areas of the diagnostics. So if the tire pressure is low I want the tires on a picture of the car to turn red. I want to develop this in C# since that is what I'm the most familiar with. Any suggestions on what might be the best way to do this? It would also be nice if the method scaled with resizing the window.
<Image x:Name ="Bubble" Height="445" HorizontalAlignment="Left" Margin="42,12,0,0" Stretch="Fill" VerticalAlignment="Top" Width="654" Source="/WpfApplication1;component/Images/bubble.png" Panel.ZIndex="0" Opacity="1"/>
<Image x:Name="Smiley" Height="445" HorizontalAlignment="Left" Margin="42,12,0,0" Stretch="Fill" VerticalAlignment="Top" Width="654" Source="/WpfApplication1;component/Images/bubble.png" Panel.ZIndex="1" Opacity="0"/>
<Button Content="Button" Height="35" HorizontalAlignment="Left" Margin="10,46,0,0" Name="button1" VerticalAlignment="Top" Width="24" Click="button1_Click" />
<Button Content="Button" Height="50" HorizontalAlignment="Left" Margin="14,118,0,0" Name="button2" VerticalAlignment="Top" Width="22" Click="button2_Click" />
And then to change the opacity.
Bubble.Opacity = 0.0;
Smiley.Opacity = 1.0;
One way of doing this is having multiple images, and fading the opacity. You just need to make sure the image format supports transparency (png-s do nicely). Let's say you have a car image, and separate overlays for front and rear wheel. Keeping all images the same size for easy alignment.
You'll get something like
<Image x:Name="car" Source="car.png" Panel.ZIndex="0"/>
<Image x:Name="frontwheel" Source="frontwheel.png" Panel.ZIndex="1" Opacity="0"/>
<Image x:Name="rearwheel" Source="readwheel.png" Panel.ZIndex="2" Opacity="0"/>
Then in the code
frontwheel.Opacity=1.0;
Edit: here's a snippet from some code of mine. I add graphics to the canvases in the code-behind
<Grid Margin="20">
<Image Name="image1" Width="640" Height="640"
Opacity="{Binding Path=Value, ElementName=OpSlider}"
HorizontalAlignment="Center"
/>
<Canvas Name="MarkerLayer"
Opacity="{Binding Path=Value, ElementName=DotOverlaySlider}"
/>
<Canvas x:Name="Squares"
Opacity="{Binding Path=Value, ElementName=OverlayOpSlider}"
/>
</Grid>
Opacity here is bound to sliders
<Slider x:Name="OpSlider" Width="150" SmallChange="0.05" Maximum="1" Value="0.5" />
<Slider x:Name="OverlayOpSlider" Width="150" SmallChange="0.05" Maximum="1" Value="1" />
<Slider x:Name="DotOverlaySlider" Width="150" SmallChange="0.05" Maximum="1" Value="0.8" />
So I have this Xaml inside a ListBox Item Template:
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="22" Width="Auto">
<Image Margin="-2,0,0,0" Source="{Binding Path=ADsPath, Converter={StaticResource ImxConverter}}" HorizontalAlignment="Left" Width="22" />
<TextBlock Margin="20,3,0,0" Text="{Binding Path=DisplayValue}" Width="Auto" />
<Rectangle Fill="White" Stroke="White" Margin="-2,0,-2,0.5" VerticalAlignment="Bottom" Height="1" Width="Auto" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
The idea is that the rectangle, provides a thin white line across the bottom of the entire ListBox Item; however, with the Xaml above, it only extends as long as the text, not to the full width of the ListBox.
Setting your width to Auto basically tells it to only be large enough to fit everything inside. I think you need to set your Grid's HorizontalAlignment to Stretch for it to work properly.
Edit:
I did a small sample app. Here's how I would do what you are trying to do:
On your actual listbox, I would have the HorizontalContentAlignment property set to Stretch
and
I would change your Grid to a DockPanel:
<ListBox.ItemTemplate>
<DataTemplate>
<DockPanel Height="22" HorizontalAlignment="Stretch">
<Rectangle Fill="White" Stroke="White" Margin="-2,0,-2,0.5" DockPanel.Dock="Bottom" Height="1"/>
<Image Margin="-2,0,0,0" Height="20" DockPanel.Dock="Left" Width="22" />
<TextBlock Margin="20,3,0,0" Text="Daniel Manning" DockPanel.Dock="Left"/>
</DockPanel>
</DataTemplate>
</ListBox.ItemTemplate>
Have you tried removing Width="Auto"? Auto is saying "only make me as big as I need to be" which, in your case, is determined by the length of the text. The default is "Stretch" which means "hey container, do me a favor and make me as wide as you are".