Vector Image in WPF Button Template Not Always Showing - c#
I have WPF application with a couple of buttons on which there's no text, only a vector-based image (using a Path object), the ControlTemplate looks like this:
<ControlTemplate x:Key="IconButtonContentTemplate" TargetType="{x:Type ButtonBase}">
<Grid Background="{Binding Background, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}">
<Path HorizontalAlignment="Center" VerticalAlignment="Center"
Width="{Binding ActualWidth, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Height="{Binding ActualHeight, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Data="{Binding (components:ImageButtonAttachedProperties.ImagePathData), RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}"
Stretch="Uniform" Fill="{Binding Foreground, RelativeSource={RelativeSource AncestorType={x:Type ButtonBase}, Mode=FindAncestor}}" />
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Foreground" Value="LightGray" />
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Foreground" Value="Gray" />
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
<Style TargetType="{x:Type Button}" x:Key="ClockButtonStyle" BasedOn="{StaticResource IconButtonStyle}">
<Setter Property="Template" Value="{StaticResource IconButtonContentTemplate}" />
<Setter Property="Foreground" Value="White" />
<Setter Property="components:ImageButtonAttachedProperties.ImagePathData" Value="M69.349,65.092C68.714,65.092,68.072,64.925,67.487,64.577L46.294,51.946 46.294,21.239C46.294,19.227 47.925,17.595 49.938,17.595 51.949,17.595 53.581,19.227 53.581,21.239L53.581,47.807 71.217,58.317C72.945,59.348 73.512,61.585 72.483,63.312 71.799,64.457 70.589,65.092 69.349,65.092z M49.938,3.877C24.573,3.877 3.938,24.513 3.938,49.877 3.938,75.241 24.573,95.877 49.938,95.877 75.302,95.877 95.938,75.241 95.938,49.877 95.938,24.513 75.302,3.877 49.938,3.877z M52.876,88.467C52.882,88.395 52.897,88.324 52.897,88.25 52.897,86.615 51.572,85.29 49.937,85.29 48.302,85.29 46.977,86.615 46.977,88.25 46.977,88.324 46.994,88.395 46.999,88.467 27.994,87.032 12.783,71.822 11.349,52.817 11.423,52.822 11.492,52.838 11.567,52.838 13.202,52.838 14.527,51.513 14.527,49.878 14.527,48.243 13.201,46.918 11.567,46.918 11.492,46.918 11.422,46.935 11.349,46.94 12.783,27.933 27.994,12.722 47,11.287 46.995,11.36 46.978,11.43 46.978,11.504 46.978,13.139 48.304,14.464 49.938,14.464 51.572,14.464 52.897,13.138 52.897,11.504 52.897,11.429 52.881,11.36 52.876,11.287 71.882,12.722 87.093,27.932 88.528,46.938 88.455,46.933 88.385,46.916 88.311,46.916 86.676,46.916 85.35,48.242 85.35,49.876 85.35,51.51 86.676,52.836 88.311,52.836 88.385,52.836 88.455,52.82 88.528,52.815 87.094,71.822 71.883,87.032 52.876,88.467z" />
</Style>
My issue is that on very rare occasions the button image is not shown (99% of the time it does). The button can still be clicked, but the image on it is not shown.
I'm not sure what's causing this. The data binding on the vector image? Or the data binding on the fill color?
ImageButtonAttachedProperties.ImagePathData is System.Windows.Media.Geometry object, which describes the vector-image.
public static class ImageButtonAttachedProperties
{
public static readonly DependencyProperty ImagePathDataProperty =
DependencyProperty.RegisterAttached("ImagePathData", typeof(Geometry), typeof(ImageButtonAttachedProperties), new UIPropertyMetadata(null));
public static Geometry GetImagePathData(DependencyObject obj)
{
return (Geometry)obj.GetValue(ImagePathDataProperty);
}
public static void SetImagePathData(DependencyObject obj, Geometry value)
{
obj.SetValue(ImagePathDataProperty, value);
}
}
Any idea what I'm doing wrong here?
You're binding the desired dimensions of a template element to the final dimensions of the templated parent; this creates a circular dependency. Don't do that.
The size of the templated control (e.g., your Button) depends on the desired size of its template content (e.g., your Path). If you make the size of the content dependent on the size of the templated parent, you're just asking for trouble.
If I had to guess, you may be getting caught in a layout loop, where one layout update triggers another to occur on the next tick. These aren't always obvious (except, perhaps, by looking at CPU usage), because of the way layout updates are scheduled. I've seen this happen before, e.g., when the 'Arrange' pass invalidates the results of the 'Measure' pass. The visible effects can be rather unpredictable: sometimes things appear to be working fine, until they don't.
Get rid of the Width and Height bindings on your Path, and set the horizontal and vertical alignments to Stretch.
You should:
1) Remove the databinding on the fill color to determine if you can still re-produce the problem.
I you cannot, try it the other way around:
2) Remove the databinding on the Path (use a constant Path for testing) and test with the databinding on the fill color.
If 1) and 2) could not reproduce your problem then try:
3) Use constant values on the fill color and the databinding on the Path
If you were able to re-produce the problem in 1) or 2) you now know at least the source of the problem. The same is true for 3) but it is more complicated because 3) implies that both databindings somehow influence each other ...
4) Its also possible that your problem is burried deeper in your themes defintion - so switching custom themes off and using only Generics themes is also a way to test in order to learn the source of the issue.
5) I would also try to layout the Path on the Background of the Button to see if this behaves better with regard to your problem - see my last comment in this post:
Image fill the space on the button in WPF
You really need to post a small demo app to get a deterministic answer, otherwise, I am afraid that it is impossible to tell the exact source given only the snippets shown
I'm not sure what's causing this.
At some point the values of either ActualWidth or ActualHeight (on any control) are actually zero and those properties are read only type dependency properties. That happens when the control is Loaded, Measured, Arranged, or Rendered.
As per the FrameworkElement.ActualWidth Property
Because ActualWidth is a calculated value, you should be aware that
there could be multiple or incremental reported changes to it as a
result of various operations by the layout system. The layout system
may be calculating required measure space for child elements,
constraints by the parent element, and so on.
The question is that something is jiggering your button and causing a resize and you win the lottery by catching a zero value.
As a course of something to try, I provided an answer about housing a vector image Best way to use a vector image in WPF in two different ways, and the example I used had 3 vectors in a resizable window and to my knowledge they didn't flash except to redraw. But I had the height/width set to stretch.
Maybe change how the vector is held?
Related
ItemsControl, ItemContainerStyle and ItemTemplate
Since I'm still struggling with understanding how ItemContainerStyle works, I tried to go to the root component that defines its behavior, that is ItemsControl. The simplest application of style I can think of is trying to apply a couple of settings, let say the Background and the Foreground to the item. <Window.DataContext> <local:VM></local:VM> </Window.DataContext> <DockPanel > <ItemsControl ItemsSource="{Binding Items}"> <ItemsControl.ItemContainerStyle> <Style> <Setter Property="Control.Foreground" Value="red"/> <Setter Property="Control.Background" Value="yellow"/> </Style> </ItemsControl.ItemContainerStyle> </ItemsControl> </Window> The underlying class for the data is: public class VM { public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>(); public VM() { Items.Add("first"); Items.Add("second"); Items.Add("third"); } } The result: Ok, background is not applied, but this is not what I wanted to check and BTW in WPF there seem to be more exceptions than rules. (And BTW2 i've already fighted with assigning the background of a ListBox selected item, that requires to retemplate the whole thing, maybe here it's similar? If you know the answer it's appreciated, but I leave it for now because it's taking me off track). Let's also have a look at the Visual Tree: That is, for ItemsControl the items don't get a 'wrapper element'; if we do the same with a ListBox, for each item of the collection, it will be constructed a ListBoxItem. Now let's try to template the item by adding this (just after </ItemsControl.ItemContainerStyle>) : <ItemsControl.ItemTemplate> <ItemContainerTemplate> <Label MaxWidth="100" Content="{Binding}"/> </ItemContainerTemplate> </ItemsControl.ItemTemplate> This is the result (they are moved in the center because of the MaxWidth="100"; I wanted to see if there was something behind): The style is not applied anymore. Let's have a look at the Viusal Tree: This visual tree is not surprising, we just replaced default representation that before was a TextBlock. In its place now we find a Label with its own standard sub-tree. What's surprising is that at least Foreground should apply to the label too, but unfortunately it doesn't. What's going on then? I've read a very similar question here: Can't use ItemTemplate and ItemContainerStyle together? It differs from this in that it tries to assign the ContentTemplate. Since I'm still struggling with the basic behavior here (and I didn't understand the answer there except that there is some sort of copy-problem) I decided to put this more basic question. However it seems there is a style-targeting problem here and not a copy problem; this is because if I keep the ItemTemplate, but replace the Label with a TextBlock (that leads to the very same VisualTree of the non-templated version) I get back my foreground red color! <ItemsControl.ItemTemplate> <ItemContainerTemplate> <TextBlock MaxWidth="100" Text="{Binding}"/> </ItemContainerTemplate> </ItemsControl.ItemTemplate> Getting warmer? So it seems that the framework checks if the component is TextBlock and if not doesn't apply the style. But this is the default behavior when applying implicit styles: a stile with (TargetType == the type of the control being styled). In this case it seems like the framework assumes that the TargetType is TextBlock, and never reconsiders this assumption even if ItemTemplate is set. In order to better understand how the style-target works here I tryed to set the style's TargetType explicitly, who knwos, so let's try this: <ItemsControl.ItemContainerStyle> <Style TargetType="Label"> <Setter Property="Label.Foreground" Value="red"/> <Setter Property="Label.Background" Value="yellow"/> </Style> </ItemsControl.ItemContainerStyle> See the TargetType="Label"? Great. It gives the error: Cant apply to ContentPresenter a style intended for Label. (translated from italian, maybe not the exact wording in english. plz replace with the exact one if you have it at hand). That is, it expects this: <ItemsControl.ItemContainerStyle> <Style TargetType="ContentPresenter"> <Setter Property="Label.Foreground" Value="red"/> <Setter Property="Label.Background" Value="yellow"/> </Style> </ItemsControl.ItemContainerStyle> It somewhat makes sense, because the root node of each item, according to the visual tree shown before is actually ContentPresenter. At this point I'm quite confused: how is it supposed to work? The idea for the moment is that it doesn't. The behavior for the subclasses like ListBox seems to be more sensible: it styles the container of the item; here a container for the item doesn't exist. That's just my guess because i couldn't find any documentation saying this.
You're looking at your items and thinking about them when setting the ItemContainerStyle. But of course this is their container you're setting a style on. The container of each item. You don't really care about your container because it's not doing much. Maybe a concrete example of a use case would be clearer than theory. If you look at: https://i.imgur.com/UZ6Nqrc.png Those red and blue rectangles are units in this game. Those are a variety of nato symbols indicating infantry, artillery cavalry etc. An itemcontainerstyle is used to position them. The whole panel on the left has an itemscontrol with a canvas as it's itemspanel ( instead of the default stackpanel ). There is a viewmodel for each unit and a collection of these is bound to the itemssource of that itemscontrol. A unit viewmodel has an X and Y property which is used to position the unit within that canvas. The position of a unit is defined by a point which is the centre of it's view. Glossing over exactly why that is, I think this is interesting because the unit's viewmodel doesn't need to calculate the offset from centre to top left corner. This is done by a converter in the view and applied using a style: <Style TargetType="ContentPresenter" x:Key="CenteredContentPresenter"> <Setter Property="Canvas.Top"> <Setter.Value> <MultiBinding Converter="{local:MultiAddConverter}"> <Binding Path="Y" Mode="TwoWay"/> <Binding Path="ActualHeight" Converter="{local:MultiplyConverter Multiplier=-.5}" RelativeSource="{RelativeSource Self}" Mode="TwoWay" /> </MultiBinding> </Setter.Value> </Setter> <Setter Property="Canvas.Left"> <Setter.Value> <MultiBinding Converter="{local:MultiAddConverter}"> <Binding Path="X" Mode="TwoWay"/> <Binding Path="ActualWidth" Converter="{local:MultiplyConverter Multiplier=-.5}" RelativeSource="{RelativeSource Self}" Mode="TwoWay" /> </MultiBinding> </Setter.Value> </Setter> </Style> Elsewhere, in the map editor trees are positioned in a similar manner.
The ItemContainerStyle is applied to the item containers that gets created by the virtual GetContainerForItemOverride method. In the ItemsControl base class, this method returns a ContentPresenter: protected virtual DependencyObject GetContainerForItemOverride() { return new ContentPresenter(); } In the derived ListBox class, it returns a ListBoxItem: protected override DependencyObject GetContainerForItemOverride() { return new ListBoxItem(); } The TargetType of the ItemContainerStyle must match the type of the DependencyObject returned from this method. Otherwise, you'll get an exception when the style is applied to the container(s) at runtime.
WPF Performance using ResourceDictionary
In my WPF application I have a lot of XAML, So I'm little confused in using ResourceDictionary in WPF, Does using it fasted my application or slow it down. EX: Lets say we have the following style for a TextBlock that we want to apply it to a lot of TextBlock's in my application: <Style TargetType="{x:Type TextBlock}" x:Key="MainText"> <Setter Property="FontSize" Value="12" /> <Setter Property="Height" Value="20" /> <Setter Property="FontWeight" Value="Bold" /> <Setter Property="FontFamily" Value="Droid Arabic Kufi" /> <Setter Property="Foreground" Value="#FF383838"/> <Setter Property="HorizontalAlignment" Value="Center"/> </Style> What is the best approach for my application performance, Declared the style in ResourceDictionary like above and using it as following: <TextBlock Style="{StaticResource MainText}" Text="Hello"/> Or repeat the style (FontWeight, FontFamily, Foreground ... ) in each one? Thanks in advance
There isn't a performance difference for using it either way like that. Performance strains on ResourceDictionaries are used only when the view is Initialized. The reason for the strain is because it looks first inward and then outward for the resource until it finds the first one of that key. So if the resource is in the control then it's found faster, then it searches the parent of that control and so on to the Window (usually MainWindow), and finally the Application. (Meaning the closer to the control the resource is the faster it's found and used.. but that speed is NOT an issue. It only really becomes an issue in apps that have a LOT of shared ResourceDictionary's at the App level.) There are very efficient and clever ways to get around this constraint but it's not an issue unless your views are rendering slowly based solely on this. Once the view is rendered it has a direct reference to the source and there are no more checks. Now if the control is created, while the view is already rendered, such as a Template in an ItemsControl, and the Templates are referencing those resources then the list may show the penalty upon updating and other things of that nature. To the point... Don't worry about performance unless the ResourceDictionary's are at the App level AND you have a lot of them.
WPF Image Control: Trigger stops working once Source is modified in code
I am experiencing some issues when changing the 'Source' property of a WPF Image control. I have three image sources defined: <Window.Resources> <BitmapImage x:Key="eyeSelImage" UriSource="/Images/eye-Sel.png" /> <BitmapImage x:Key="eyeSelHlImage" UriSource="/Images/eye-SelHl.png" /> <BitmapImage x:Key="eyeDisabled" UriSource="/Images/eye-Disabled.png" /> </Window.Resources> My Image definition looks like this: <Image x:Name="testImage" Width="100" Height="100"> <Image.Style> <Style TargetType="{x:Type Image}"> <Setter Property="Source" Value="{StaticResource eyeSelImage}"/> <Style.Triggers> <Trigger Property="IsEnabled" Value="False"> <Setter Property="Source" Value="{StaticResource eyeDisabled}"/> </Trigger> </Style.Triggers> </Style> </Image.Style> </Image> It works as expected. If the image control is disabled, the image changes. Once it becomes enabled, it changes back. I tested this by making a button (btn_DisableEnable) which, when clicked, toggles the 'IsEnabled' property of the 'testImage'. However as soon as I change the 'Source' of my 'testImage' image control in code, the 'IsEnabled' trigger seems to stop working. I made another button and in its 'Click' event handler I do the following: BitmapImage tempImage = new BitmapImage(); tempImage.BeginInit(); tempImage.UriSource = new Uri("pack://application:,,,/testApp1;component/Images/eye-SelHl.png"); tempImage.EndInit(); testImage.Source = tempImage; After pressing this button, the image's source properly changes to the 'eyeSelHlImage' resource. However the image no longer changes to its disabled representation and back when the 'btn_DisableEnable' is clicked. What could be the problem? All help is greatly appreciated. Thanks!
Since dependency properties can be set by different mechanisms (triggers, styles, themes, inheritance and so on), it has been defined a "setting" precedence list. You can find it here. As you can see local value - i.e. if you set the directly property with code or by XAML, or by using the SetValue method - has an higher precedence rather than styles or triggers. So setting the Source property by code has precedence on what value your trigger may set. This is the reason why, after you call your code, your trigger does not work any more.
Edit Button Visual State without Blend
One can edit Foreground and Background etc... for a Button's default state without Blend using just the properties pane or code, but it is it possible to edit the colors for the other states without Blend? For example, all I want is a button to turn gray on "Mouse Over". Anything I have seen on Stack Overflow or on the Intertubes uses Blend. I want to do it without it. Is it possible?
An easy way to do this is using Style Triggers. <Style x:Key="HoverButtonStyle" TargetType="{x:Type Button}"> <Style.Triggers> <Trigger Property="IsMouseOver" Value="True"> <Setter Property="Background" Value="Gray"/> </Trigger> </Style.Triggers> </Style> The style is defined in your Resources. And then when you define your button in XAML, you assign the style, like this: <Button x:Name="MyButton" Style="{DynamicResource HoverButtonStyle}"/> This style will change the Background brush of your Button when the mouse is over it. There are many more advanced things you can do with triggers, but this is a very simple example. If you google WPF Style Triggers, you'll find many examples, including this one which is pretty thorough. There are other ways to do this, for example using the VisualStateManager, as described here, however if you're just trying to change the Background on hover, a style trigger is probably the simplest way.
Set a style color based on an object
I want to set the Background color of an object's style, to be the color of the Window Foreground. So.... how to get the color of one object and use it as the value in a style? <Setter Property="Background" Value="????Window Foreground Color????" /> I've tried different binding combinations, but none of them have worked yet. In code-behind this value would be this.Foreground, but in XAML style?
Quickest way would be to give you parent Window a name and use that in binding. Something like this (if you named your window "Root"): <Setter Property="Background" Value="{Binding ElementName=Root, Path=Foreground}" />