#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), которые меняются во время выполнения программы. При запуске все значения доступны, как я и ожидал.