简体   繁体   中英

Accessing a static object from Style/template in Generic.xml?

I have a double called LoadAnimAngle which simply holds the angle of a spinning loading icon, which gets rotated over time. This variable is defined in my MainViewModel class. I'm using this same variable across all places that has a spinning loading icon.

I need it inside a custom control that is defined in Generic.xml with a style/template. Here is the part where I'm binding to LoadAnimAngle :

<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
    <v:ColoredImage.RenderTransform>
       <RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}}"/> //here is the error
    </v:ColoredImage.RenderTransform>
</v:ColoredImage>

The custom control has a property that is binding to my instance of MainViewModel , like so:

public MainViewModel MainViewModel { get { return MainViewModel.instance; } }

Inside the constructor of MainViewModel I simply set:

instance = this;

The problem is that Generic.xml gets loaded before my MainViewModel class, causing the instance to be null for the frame before the graphics have loaded, after everything is done loaded, everything works. How could I resolve this problem?

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=MainViewModel.LoadAnimAngle; DataItem=null; target element is 'RotateTransform' (HashCode=66939890); target property is 'Angle' (type 'Double')

Notice: I do understand that the error is harmless and does not effect anything for the end user, however seeing that error every time I debug causes me emotional pain.

I need to somehow load MainViewModel before Generic, OR, tell xaml to not try to get the data from LoadAnimAngle until MainViewModel != null.

EDIT

I get the same error after I made changes so that I do not directly bind to the instance of MainViewModel . So I think my evaluation of the case of the problem is wrong.

I added

    public double LoadAnimAngle
    {
        get
        {
            if (MainViewModel.instance != null)
            {
                return MainViewModel.instance.LoadAnimAngle;
            }
            return 0;
        }
    }

to the view model (instead of return MainViewModel.instance )

Then I changed the binding to:

Angle="{Binding Path=LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"

I get the same error:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=LoadAnimAngle; DataItem=null; target element is 'RotateTransform' (HashCode=21529561); target property is 'Angle' (type 'Double')

If the problem is not that the MainViewModel.instance is NULL, then what is it that causes the problem? I have problems decoding the language in the error message. What exactly is wrong and why?

EDIT 2

Relevant context (?)

    <Style TargetType = "{x:Type v:ComPortButton}" >
    <Setter Property = "Background" Value = "{StaticResource Milky}"/>
    <Setter Property = "ColorPalette" Value = "{StaticResource MilkyPalette}"/>
    <Setter Property = "Foreground" Value = "{StaticResource Black}"/>
    <Setter Property = "BorderColor" Value = "{StaticResource Milky}"/>
    <Setter Property="IsBasicTextButton" Value="False"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type v:ComPortButton}">
                <Grid>
                    <Grid Visibility="{Binding Path=IsBasicTextButton, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource InverseBooleanToVisibility}}">
                        <Border BorderBrush="{TemplateBinding BorderColor}" Background="{TemplateBinding Background}" Width="128" Height="140" BorderThickness="1"/>

                        //REMOVED IREELEVANT CODE

                        <v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
<v:ColoredImage.RenderTransform>
   <RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}}"/> //here is the error
</v:ColoredImage.RenderTransform>
</v:ColoredImage>

                    </Grid>
                    
                    //REMOVED IRRELEVANT CONTROL
                    </Grid>

                    //REMOVED IRRELEVANT CONTEXT MENU
                    
                </Grid>
                //REMOVED IRRELEVANT TRIGGERS
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

EDIT 3

The source of the error seems to be completely different from I first thought. The error seems to have something to do with RenderTransform , because I can access the property without errors from other places.

Like this:

// NO ERROR FOR TEXT BLOCK
<TextBlock Text="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
                        
<v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
    <v:ColoredImage.RenderTransform>
        // ERROR FOR ROTATETRANSFORM
        <RotateTransform Angle="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
    </v:ColoredImage.RenderTransform>
</v:ColoredImage>

But I also get the error when I do not reference MainViewModel . I created a new property like this:

    public double LoadAnimAngle
    {
        get
        {
            return 0;
        }
    }

Then I used it in the Template like this:

                            <v:ColoredImage Image="{StaticResource LoadingIcon}" Color="{StaticResource DarkBlueClick}" RenderTransformOrigin="0.5, 0.5" VerticalAlignment="Center" Width="32" Height="32" Margin="0,0,0,0" Visibility="{Binding IsBusy, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource BooleanToVisibility}}">
                            <v:ColoredImage.RenderTransform>
                                <RotateTransform Angle="{Binding LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
                            </v:ColoredImage.RenderTransform>
                        </v:ColoredImage>

But i get the EXACT same error!

So, the property works, everything works. It's just that RenderTransform is like outside of the VisualTree for the first frame when it is instantiated? Or something like that, i guess? Something different is happening in RenderTransform that makes it so it doesnt like my binding.

And i probably wasnt clear about the structure.

ComPortButton is a Custom Control (.cs file with Template/Style in Generic.xml). ComPortButton uses ComPortVM as it's DataContext . I want to access the spinning value globally, different controls, different windows, different everything, globally. I have a MainViewModel in which i currently store the value, since it gives global access, since it

EDIT 4

Solved it and posted the solution below

Your control must be independent from the concrete view model type. Instead, bind internals of the control to dependency properties on this control. Then let the external view model bind to this properties (or set them locally).
This way you remove the tight coupling between the control and the DataContext , which drastically simplifies the implementation of the control. It also allows the control to be reused with any DataContext (view model).

ComPortButton.cs

class ComPortButton : Control
{
  public double Angle
  {
    get => (double)GetValue(AngleProperty);
    set => SetValue(AnglePropertyKey, value);
  }

  protected static readonly DependencyProperty AnglePropertyKey = DependencyProperty.RegisterReadOnly(
  "Angle",
  typeof(double),
  typeof(ComPortButton),
  new PropertyMetadata(default));

  public static readonly DependencyProperty AngleProperty = AnglePropertyKey..DependencyProperty;

  public double ProgressPercentage
  {
    get => (double)GetValue(ProgressPercentageProperty);
    set => SetValue(ProgressPercentageProperty, value);
  }

  public static readonly DependencyProperty ProgressPercentageProperty = DependencyProperty.Register(
  "ProgressPercentage",
  typeof(double),
  typeof(ComPortButton),
  new PropertyMetadata(default(double), OnProgressPercentageChanged));

  private static void OnProgressPercentageChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
  {
    double percentage = (double)e.NewValue / 100;

    // Enforce an angle between 0°-360°
    this.Angle = Math.Max(0, Math.Min(360, 360 * percentage));
  }
}

Generic.xaml

<Style TargetType="ComPortButton">
  <Setter Property="Template">
    <Setter.Value>
      <ControlTemplate TargetType="{x:Type v:ComPortButton}">
        <v:ColoredImage>
          <v:ColoredImage.RenderTransform>
            <RotateTransform Angle="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Angle}" />
          </v:ColoredImage.RenderTransform>
        </v:ColoredImage>
      </ControlTemplate>
    </Setter.Value>
  </Setter>
</Style>

Usage example

<Window>
  <Window.DataContext>
    <MainViewModel />
  </Window.DataContext>

  <ComPortButton ProgressPercentage="{Binding ProgressPercentageValue}" />
</Window>

After i figured it out that it was RenderTransform that was the problem and not anything else it was easy to find solutions online, seems that many people have had the same problem.

Here is the Thread that helped me solve it

The problem had something to do with VisualTree, that RenderTransform in the Template isnt hooked up to the VisualTree before the entire Control is loaded. Or something like that.

When binding like this to RotateTransform:

    <v:ColoredImage.RenderTransform>
        <RotateTransform Angle="{Binding LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}}"/>
    </v:ColoredImage.RenderTransform>

The problem occurs. But for some reason that i did not understand, you can get rif of the error by binding to RenderTransform instead. But for that you need a Converter.

[ValueConversion(typeof(double), typeof(RotateTransform))]
public class AngleToTransform : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        return new RotateTransform((double)value);
    }

    public object ConvertBack(object value, Type targetType, object parameter,
        System.Globalization.CultureInfo culture)
    {
        throw new NotSupportedException();
    }
}

Then use the converter like so:

<v:ColoredImage RenderTransform="{Binding MainViewModel.LoadAnimAngle, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource AngleToTransform}}"/>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM