OnApplyTemplate() выполняется перед привязкой правильных значений

#c# #wpf #xaml #mvvm

#c# #wpf #xaml #mvvm

Вопрос:

оригинальный вопрос с выдержкой из View

Я использую адаптированную версию этого пользовательского элемента управления в проекте шаблона MVVM с caliburn micro framework.

 <gauge:STGaugeControl x:Name="Gauge"
    Radius="120"
    ScaleRadius="105" 
    ScaleStartAngle="120" 
    ResetPointer1OnStartUp="True"
    ScaleSweepAngle="300"
    Pointer1Length="95" 
    Pointer2Length="95" 
    Pointer3Length="95" 
    PointerCapRadius="25" 
    MinValue="{Binding MinGauge, FallbackValue=-6000}" 
    MaxValue="{Binding MaxGauge, FallbackValue=6000}" 
    MajorDivisionsCount="{Binding DivCount, FallbackValue=12}" 
    MinorDivisionsCount="5" 
    CurrentValue1="{Binding Value1}"
    CurrentValue2="{Binding Value2}"
    CurrentValue3="{Binding Value3}"
    ScaleLabelRadius="80"
    ...
    >
  

Это часть моего представления. Теперь, когда код выполняется, значения привязаны и работают должным образом. Но масштаб отображается в методе, который вызывается в OnApplyTemplate() пользовательского элемента управления и по какой-то причине не имеет доступа к привязанным значениям. При удалении резервной копии используется либо резервное значение, либо нулевое значение по умолчанию.
Например, когда я вызываю метод масштабного рисования при наведении курсора мыши на событие, он работает так, как задумано.
Вызывается ли OnApplyTemplate() для раннего доступа к значениям привязки или есть что-то еще, что я делаю неправильно и пропустил во всех руководствах?

пользовательский управляющий код и фрагменты разметки

Пользовательский код управления

 [TemplatePart(Name = "LayoutRoot", Type = typeof(Grid))]
[TemplatePart(Name = "Pointer1", Type = typeof(Path))]
[TemplatePart(Name = "Pointer2", Type = typeof(Path))]
[TemplatePart(Name = "Pointer3", Type = typeof(Path))]
[TemplatePart(Name = "RangeIndicatorLight", Type = typeof(Ellipse))]
[TemplatePart(Name = "PointerCap", Type = typeof(Ellipse))]
public class STGaugeControl : Control
{
#region Private variables
private Grid rootGrid;
*snip*

#region Dependency properties
public static readonly DependencyProperty MinValueProperty =
    DependencyProperty.Register("MinValue", typeof(double), typeof(STGaugeControl), null);

public static readonly DependencyProperty MaxValueProperty =
    DependencyProperty.Register("MaxValue", typeof(double), typeof(STGaugeControl), null);

public static readonly DependencyProperty MajorDivisionsCountProperty =
    DependencyProperty.Register("MajorDivisionsCount", typeof(double), typeof(STGaugeControl), null);

public static readonly DependencyProperty MinorDivisionsCountProperty =
    DependencyProperty.Register("MinorDivisionsCount", typeof(double), typeof(STGaugeControl), null);
*snip*

#region Wrapper properties
public double MinValue
{
    get
    {
        return (double)GetValue(MinValueProperty);
    }
    set
    {
        SetValue(MinValueProperty, value);
    }
}

public double MaxValue
{
    get
    {
        return (double)GetValue(MaxValueProperty);
    }
    set
    {
        SetValue(MaxValueProperty, value);
    }
}
*snip*

#region Constructor
static STGaugeControl()
{
    DefaultStyleKeyProperty.OverrideMetadata(typeof(STGaugeControl), new FrameworkPropertyMetadata(typeof(STGaugeControl)));
}

#region Methods
private static void OnCurrentValue1PropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    //Get access to the instance of CircularGaugeConrol whose property value changed
    STGaugeControl gauge = d as STGaugeControl;
    gauge.OnCurrentValue1Changed(e);
}
*snip*

public override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    //Get reference to known elements on the control template
    rootGrid = GetTemplateChild("LayoutRoot") as Grid;
    pointer1 = GetTemplateChild("Pointer1") as Path;
    pointer2 = GetTemplateChild("Pointer2") as Path;
    pointer3 = GetTemplateChild("Pointer3") as Path;
    pointerCap = GetTemplateChild("PointerCap") as Ellipse;
    lightIndicator = GetTemplateChild("RangeIndicatorLight") as Ellipse;

    //Draw scale and range indicator
    DrawScale();

    //Set Zindex of pointer and pointer cap to a really high number so that it stays on top of the 
    //scale and the range indicator
    Canvas.SetZIndex(pointer1, 100001);
    Canvas.SetZIndex(pointer2, 100002);
    Canvas.SetZIndex(pointer3, 100003);
    Canvas.SetZIndex(pointerCap, 100005);

    if (ResetPointer1OnStartUp)
    {
        //Reset Pointer
        MovePointer1(ScaleStartAngle);
    }

    if (ResetPointer2OnStartUp)
    {
        //Reset Pointer
        MovePointer2(ScaleStartAngle);
    }

    if (ResetPointer3OnStartUp)
    {
        //Reset Pointer
        MovePointer3(ScaleStartAngle);
    }
}
*snip*

//Drawing the scale with the Scale Radius
public void DrawScale()
{
    //Calculate one major tick angle 
    Double majorTickUnitAngle = ScaleSweepAngle / MajorDivisionsCount;

    //Obtaining One minor tick angle 
    Double minorTickUnitAngle = ScaleSweepAngle / MinorDivisionsCount;

    //Obtaining One major ticks value
    Double majorTicksUnitValue = (MaxValue - MinValue) / MajorDivisionsCount;
    majorTicksUnitValue = Math.Round(majorTicksUnitValue, ScaleValuePrecision);

    Double minvalue = MinValue; ;

    // Drawing Major scale ticks
    for (Double i = ScaleStartAngle; i <= (ScaleStartAngle   ScaleSweepAngle); i = i   majorTickUnitAngle)
    {

        //Majortick is drawn as a rectangle 
        Rectangle majortickrect = new Rectangle();
        majortickrect.Height = MajorTickSize.Height;
        majortickrect.Width = MajorTickSize.Width;
        majortickrect.Fill = new SolidColorBrush(MajorTickColor);
        Point p = new Point(0.5, 0.5);
        majortickrect.RenderTransformOrigin = p;
        majortickrect.HorizontalAlignment = HorizontalAlignment.Center;
        majortickrect.VerticalAlignment = VerticalAlignment.Center;

        TransformGroup majortickgp = new TransformGroup();
        RotateTransform majortickrt = new RotateTransform();

        //Obtaining the angle in radians for calulating the points
        Double i_radian = (i * Math.PI) / 180;
        majortickrt.Angle = i;
        majortickgp.Children.Add(majortickrt);
        TranslateTransform majorticktt = new TranslateTransform();

        //Finding the point on the Scale where the major ticks are drawn
        //here drawing the points with center as (0,0)
        majorticktt.X = (int)((ScaleRadius) * Math.Cos(i_radian));
        majorticktt.Y = (int)((ScaleRadius) * Math.Sin(i_radian));

        //Points for the textblock which hold the scale value
        TranslateTransform majorscalevaluett = new TranslateTransform();
        //here drawing the points with center as (0,0)
        majorscalevaluett.X = (int)((ScaleLabelRadius) * Math.Cos(i_radian));
        majorscalevaluett.Y = (int)((ScaleLabelRadius) * Math.Sin(i_radian));

        //Defining the properties of the scale value textbox
        TextBlock tb = new TextBlock();

        tb.Height = ScaleLabelSize.Height;
        tb.Width = ScaleLabelSize.Width;
        tb.FontSize = ScaleLabelFontSize;
        tb.Foreground = new SolidColorBrush(ScaleLabelForeground);
        tb.TextAlignment = TextAlignment.Center;
        tb.VerticalAlignment = VerticalAlignment.Center;
        tb.HorizontalAlignment = HorizontalAlignment.Center;

        //Writing and appending the scale value

        //checking minvalue < maxvalue w.r.t scale precion value
        if (Math.Round(minvalue, ScaleValuePrecision) <= Math.Round(MaxValue, ScaleValuePrecision))
        {
            minvalue = Math.Round(minvalue, ScaleValuePrecision);
            tb.Text = minvalue.ToString();
            minvalue = minvalue   majorTicksUnitValue;

        }
        else
        {
            break;
        }
        majortickgp.Children.Add(majorticktt);
        majortickrect.RenderTransform = majortickgp;
        tb.RenderTransform = majorscalevaluett;
        rootGrid.Children.Add(majortickrect);
        rootGrid.Children.Add(tb);


        //Drawing the minor axis ticks
        Double onedegree = ((i   majorTickUnitAngle) - i) / (MinorDivisionsCount);

        if ((i < (ScaleStartAngle   ScaleSweepAngle)) amp;amp; (Math.Round(minvalue, ScaleValuePrecision) <= Math.Round(MaxValue, ScaleValuePrecision)))
        {
            //Drawing the minor scale
            for (Double mi = i   onedegree; mi < (i   majorTickUnitAngle); mi = mi   onedegree)
            {
                //here the minortick is drawn as a rectangle 
                Rectangle mr = new Rectangle();
                mr.Height = MinorTickSize.Height;
                mr.Width = MinorTickSize.Width;
                mr.Fill = new SolidColorBrush(MinorTickColor);
                mr.HorizontalAlignment = HorizontalAlignment.Center;
                mr.VerticalAlignment = VerticalAlignment.Center;
                Point p1 = new Point(0.5, 0.5);
                mr.RenderTransformOrigin = p1;

                TransformGroup minortickgp = new TransformGroup();
                RotateTransform minortickrt = new RotateTransform();
                minortickrt.Angle = mi;
                minortickgp.Children.Add(minortickrt);
                TranslateTransform minorticktt = new TranslateTransform();

                //Obtaining the angle in radians for calulating the points
                Double mi_radian = (mi * Math.PI) / 180;
                //Finding the point on the Scale where the minor ticks are drawn
                minorticktt.X = (int)((ScaleRadius) * Math.Cos(mi_radian));
                minorticktt.Y = (int)((ScaleRadius) * Math.Sin(mi_radian));

                minortickgp.Children.Add(minorticktt);
                mr.RenderTransform = minortickgp;
                rootGrid.Children.Add(mr);


            }

        }

    }
}


}
}
  

Generic.xaml:

 <ResourceDictionary
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:SpeedTorque">
*snip*

<Style TargetType="local:STGaugeControl" >
    <Setter Property="ResetPointer1OnStartUp" Value="True" />
    <Setter Property="ResetPointer2OnStartUp" Value="True" />
    <Setter Property="ResetPointer3OnStartUp" Value="True" />
    <Setter Property="ScaleValuePrecision" Value="5" />
    *snip*
    <Setter Property="GaugeBackgroundColor" Value="Black" />
    <Setter Property="DialTextColor" Value="White" />
    <Setter Property="DialTextFontSize" Value="8" />

    <Setter Property="Template" >
        <Setter.Value>
            <ControlTemplate TargetType="local:STGaugeControl">
                <!-- Root Grid-->
                <Grid x:Name="LayoutRoot" 
                      Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius, Converter={StaticResource radiusToDiameterConverter}}" 
                      Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius, Converter={StaticResource radiusToDiameterConverter}}" >


                    <Ellipse x:Name="OuterFrame" StrokeThickness="8" 
                             Height="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius, Converter={StaticResource radiusToDiameterConverter}}" 
                             Width="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Radius, Converter={StaticResource radiusToDiameterConverter}}" 
                             Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=GaugeBackgroundColor, Converter={StaticResource backgroundColorConverter}}">

                        <Ellipse.Stroke>
                            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                <GradientStop Color="#FF636060" Offset="1"/>
                                <GradientStop Color="#FF5F5C5C" Offset="0"/>
                                <GradientStop Color="#FFEEDEDE" Offset="0.35"/>
                                <GradientStop Color="#FFA09595" Offset="0.705"/>
                            </LinearGradientBrush>
                        </Ellipse.Stroke>
                    </Ellipse>
                    *snip*
                    
                    <!-- Pointer1 -->
                    <Path x:Name="Pointer1" Stroke="#FFE91C1C" StrokeThickness="2" 
                          Width="{TemplateBinding Pointer1Length}" 
                          Height="{TemplateBinding Pointer1Thickness}" HorizontalAlignment="Center"
                          Data="M1,1 L1,10 L156,6 z" Stretch="Fill"  RenderTransformOrigin="0,0.5" 
                          RenderTransform="{Binding RelativeSource={RelativeSource TemplatedParent}, 
                        Path=Pointer1Length, Converter={StaticResource pointerCenterConverter}}"
                          Visibility="{TemplateBinding Pointer2Visibility}" >
                        <Path.Fill>
                            <LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
                                <GradientStop Color="#33890A0A" Offset="0.197"/>
                                <GradientStop Color="#33C40808" Offset="1"/>
                                <GradientStop Color="#33E32323" Offset="0.61"/>
                            </LinearGradientBrush>
                        </Path.Fill>
                    </Path>
                    *snip*

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>
</ResourceDictionary>
  

Комментарии:

1. Масштабирование и компоновка должны выполняться в Measure и Arrange, а не в OnApplyTemplate. Единственное реальное применение, которое я нашел для if, — это получение ссылок на именованные части вашего элемента управления.

2. Что вы имеете в виду под «масштаб отображается в методе, который вызывается в OnApplyTemplate()» ? OnApplyTemplate вызывается, когда для этого элемента управления установлен ControlTemplate — вы пытаетесь выразить, что масштаб является частью ControlTemplate, который присваивается этому элементу управления?

3. @Zache: не могли бы вы подробнее рассказать об этом «Измерении и упорядочивании», когда я впервые слышу об этом.

4. @elgonzo: да, масштаб является частью шаблона управления. Он не отличается от пользовательского шаблона датчика, на который я ссылался в вопросе, если вы не знаете, что там проверять, я могу добавить часть кода для понимания.

5. @Stefan, тогда, пожалуйста, добавьте шаблон управления к вашему вопросу. нецелесообразно требовать, чтобы другие посещали веб-сайт этого проекта, загружали пакет исходного кода и искали файл XAML, содержащий шаблон управления… Если управляющий шаблон длиннее 20 … 30 строк, не размещайте его полностью в своем вопросе, но вырежьте любую его часть, которая не связана с масштабом…

Ответ №1:

Я чувствую, что вам нужно привязать свой scale к свойству виртуальной машины и пересчитывать его каждый раз, когда вы меняете параметры, от которых это зависит. Я полагаю, вам нужно будет обновлять ваше свойство Scale в виртуальной машине каждый раз, когда для нового значения устанавливается значение MinGauge или MaxGauge.

Комментарии:

1. Масштаб отображается только один раз, но это зависит от сигнала, который присваивается шаблону при создании. В исходном шаблоне значения фиксированы, но мне посоветовали придерживаться схемы кода, в которой сигнал, а также минимальные и максимальные значения присваиваются при создании с помощью параметров. Кроме того, элемент gauge используется несколько раз внутри родительского представления, каждый с разными сигналами и минимальными / максимальными значениями. Значения привязаны к той же виртуальной машине, что и значения указателей (CurrentValue1 / 2 / 3), которые меняются во время выполнения программы. При запуске все значения доступны, как я и ожидал.