2

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?

Bedford
  • 1,136
  • 2
  • 12
  • 36
  • Debug the bindings: https://spin.atomicobject.com/2013/12/11/wpf-data-binding-debug Hook up some visibility/parent changed events and write them out to the console. Try a similar thing with a bitmap image. Hook up to `AppDomain.CurrentDomain.FirstChanceException`, see if there's something suspicious there... And use style-able triggers, hard-coded colors look odd when your users ask for a dark mode – Sten Petrov Oct 30 '18 at 17:43
  • 1
    inspect the visual tree using Visual Studio Live Tree or external tool like Snoop. verify that all elements are where you expect them to be, and all properties are what you expect them to be. then you will probably find the culprit – ASh Oct 30 '18 at 17:51
  • 1
    According to your statistic, at least 2 buttons [in this window](https://imgur.com/NYTQ6yA) should present the problem, but it just doesn't happen, even after several executions. – jsanalytics Oct 30 '18 at 21:58
  • 1
    Cannot reproduce it, will it be possible for you to post a demo project with the issue? Also, it will be useful if you could post code for `IconButtonStyle` as it might have some triggers I'm not aware about. – Dipen Shah Oct 31 '18 at 16:23
  • try to set width and height of `Path` explicitly (without any bindings) and see if this happens again or not? – Amir Oveisi Nov 05 '18 at 18:35

3 Answers3

2

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.

Mike Strobel
  • 25,075
  • 57
  • 69
  • Stretch is the default value of the alignment properties of a Path. No need to set them. – Clemens Nov 06 '18 at 15:07
  • 1
    @Clemens True, though if we really want to be 'correct', the `[H/V]Alignment` properties on the `Path` should probably be bound to the corresponding `[H/V]ContentAlignment` properties of the templated parent. – Mike Strobel Nov 06 '18 at 19:59
0

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?

ΩmegaMan
  • 29,542
  • 12
  • 100
  • 122
  • "catching a zero value" would mean that a *final* value of ActualWidth or ActualHeight after finishing a layout pass would be zero, which makes no sense at all. Still, the Width and Height Bindings are entirely redundant. The Grid already resizes its Path child. HorizontalAlignment and VerticalAlignment are also redundant. Finally, Background and Fill should use TemplateBindings. – Clemens Oct 31 '18 at 19:37
  • Minor nitpick: `ActualWidth/Height` *are* dependency properties, but they're **read-only** dependency properties that don't get set explicitly (the values are extracted from `RenderSize`). That's significant insofar as the properties benefit from the DP change notification mechanisms. – Mike Strobel Nov 06 '18 at 14:35
  • @MikeStrobel adjusted text accordingly. Thx – ΩmegaMan Nov 06 '18 at 14:40
0

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

user8276908
  • 1,051
  • 8
  • 20