今回はグラフの目盛線の描画方法を紹介しますが,
その前に基本となるクラスを説明しないことには,
途中で意味不明なコードが出てきますので,
まずはその基本クラスを簡単に紹介します.
紹介する基本クラスは NotifyPropertyChangedDataClass 抽象クラスです.
INotifyPropertyChanged インターフェースを実装するためのもので,
プロパティ値変更を容易に実装できるように工夫しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
namespace CustomControls { using System.ComponentModel; public abstract class NotifyPropertyChangedDataClass : INotifyPropertyChanged { /// <summary> /// プロパティ値変更イベント /// </summary> public event PropertyChangedEventHandler PropertyChanged; /// <summary> /// プロパティ値変更イベント発行 /// </summary> /// <param name="propertyName"></param> protected virtual void RaisePropertyChanged(string propertyName) { var h = PropertyChanged; if (h != null) h(this, new PropertyChangedEventArgs(propertyName)); } /// <summary> /// プロパティ値変更ヘルパ /// </summary> /// <typeparam name="T"></typeparam> /// <param name="target"></param> /// <param name="value"></param> /// <param name="propertyName"></param> /// <returns></returns> public virtual bool SetProperty<T>(ref T target, T value, string propertyName) { if (target == null) { if (value == null) return false; } else { if (target.Equals(value)) return false; } target = value; RaisePropertyChanged(propertyName); return true; } } } |
ポイントは SetProperty<T>() メソッドで,
このメソッドを使ってプロパティ値の変更をする/しないの判断を含めて処理させます.
実際の使用例を交えてグラフの軸設定に関するパラメータをまとめた
AxisStyle クラスを次のように定義します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
namespace CustomControls { public class AxisStyle : NotifyPropertyChangedDataClass { private double min = 0.0; /// <summary> /// 最小値 /// </summary> public double Min { get { return min; } set { if (value < Max) { if (SetProperty(ref min, value, "Min")) RaisePropertyChanged("Width"); } } } private double max = 100.0; /// <summary> /// 最大値 /// </summary> public double Max { get { return max; } set { if (value > Min) { if (SetProperty(ref max, value, "Max")) RaisePropertyChanged("Width"); } } } /// <summary> /// 幅 /// </summary> public double Width { get { return Max - Min; } } private double step = 10.0; /// <summary> /// 刻み /// </summary> public double Step { get { return step; } set { if (value > 0.0) SetProperty(ref step, value, "Step"); } } } } |
SetProperty() メソッドはプロパティ値に変更がある場合に true を返します.
幅 Width プロパティは最小値/最大値が変更されると同時に
変更通知をおこなわなければならないため,
最小値/最大値プロパティの set アクセサの中で変更通知をおこなうようにしています.
それぞれの set アクセサで,
value に対する条件を書いていますが,
実際には IDataErrorInfo インターフェースを実装して
入力値検証の仕組みも同時に実装させます.
ここでは省略します.
それでは
改めて LineGraph カスタムコントロールを次のように定義します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
namespace CustomControls { using System.Windows; using System.Windows.Controls; /// <summary> /// 折れ線グラフコントロール /// </summary> public class LineGraph : Control { #region コンストラクタ /// <summary> /// 静的なコンストラクタ /// </summary> static LineGraph() { DefaultStyleKeyProperty.OverrideMetadata(typeof(LineGraph), new FrameworkPropertyMetadata(typeof(LineGraph))); } /// <summary> /// コンストラクタ /// </summary> public LineGraph() { } #endregion } } |
上記のように Control クラスから派生させ,
静的なコンストラクタで LineGraph の既定スタイルを読み込むようにしておきます.
LineGraph に軸設定に関するパラメータを
依存関係プロパティとして定義します.
軸は横軸/縦軸/第 2 主軸の 3 種類あるので,
3 つ定義します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
/// <summary> /// 静的なコンストラクタ /// </summary> static LineGraph() { DefaultStyleKeyProperty.OverrideMetadata(typeof(LineGraph), new FrameworkPropertyMetadata(typeof(LineGraph))); XAxisProperty = DependencyProperty.Register("XAxis", typeof(AxisStyle), typeof(LineGraph), new UIPropertyMetadata(null)); YAxisProperty = DependencyProperty.Register("YAxis", typeof(AxisStyle), typeof(LineGraph), new UIPropertyMetadata(null)); Y2AxisProperty = DependencyProperty.Register("Y2Axis", typeof(AxisStyle), typeof(LineGraph), new UIPropertyMetadata(null)); } #region 依存関係プロパティ public static readonly DependencyProperty XAxisProperty; public static readonly DependencyProperty YAxisProperty; public static readonly DependencyProperty Y2AxisProperty; #endregion #region 公開プロパティ /// <summary> /// 横軸目盛 /// </summary> public AxisStyle XAxis { get { return (AxisStyle)GetValue(XAxisProperty); } set { SetValue(XAxisProperty, value); } } /// <summary> /// 縦軸目盛 /// </summary> public AxisStyle YAxis { get { return (AxisStyle)GetValue(YAxisProperty); } set { SetValue(YAxisProperty, value); } } /// <summary> /// 第 2 主軸目盛 /// </summary> public AxisStyle Y2Axis { get { return (AxisStyle)GetValue(Y2AxisProperty); } set { SetValue(Y2AxisProperty, value); } } #endregion |
宣言時に初期化をおこなっても構いませんが,
完成後の可読性を鑑みてコンストラクタ内にて初期化をおこないます.
プロパティの型は先ほど定義した AxisStyle です.
このように依存関係プロパティを定義してから,
LineGraph の既定 Style を Generic.xaml で次のように定義します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:CustomControls"> <Style TargetType="{x:Type local:LineGraph}"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="{x:Type local:LineGraph}"> <Grid x:Name="MainContainerGrid" Background="{TemplateBinding Background}"> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="*" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="Auto" /> </Grid.ColumnDefinitions> <!-- グラフタイトル --> <TextBlock x:Name="TitleTextBlock" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="5" Text="グラフタイトル" HorizontalAlignment="Center" /> <!-- 横軸ラベル --> <TextBlock x:Name="XLabelTextBlock" Grid.Row="3" Grid.Column="2" Text="横軸ラベル" HorizontalAlignment="Center" /> <!-- 縦軸ラベル --> <TextBlock x:Name="YLabelTextBlock" Grid.Row="1" Grid.Column="0" Text="縦軸ラベル" VerticalAlignment="Center"> <TextBlock.LayoutTransform> <RotateTransform Angle="-90" /> </TextBlock.LayoutTransform> </TextBlock> <!-- 第 2 主軸ラベル --> <TextBlock x:Name="Y2LabelTextBlock" Grid.Row="1" Grid.Column="4" Text="第 2 主軸ラベル" VerticalAlignment="Center"> <TextBlock.LayoutTransform> <RotateTransform Angle="-90" /> </TextBlock.LayoutTransform> </TextBlock> <!-- 横軸目盛 --> <ItemsControl x:Name="XAxisItemsControl" Grid.Row="2" Grid.Column="2"> </ItemsControl> <!-- 縦軸目盛 --> <ItemsControl x:Name="YAxisItemsControl" Grid.Row="1" Grid.Column="1"> </ItemsControl> <!-- 第 2 主軸目盛 --> <ItemsControl x:Name="Y2AxisItemsControl" Grid.Row="1" Grid.Column="3"> </ItemsControl> <!-- データ --> <Grid Grid.Row="1" Grid.Column="2"> <!-- 境界線 --> <Border BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="Bisque" /> <Canvas x:Name="GraphCanvas"> <!-- 目盛線 --> <Path x:Name="GraphGridPath" Stroke="Red" StrokeThickness="1" StrokeDashArray="1,2" /> </Canvas> <ItemsControl x:Name="GraphItemsControl"> <!-- データ線 --> <!-- データ点 --> </ItemsControl> </Grid> </Grid> </ControlTemplate> </Setter.Value> </Setter> </Style> </ResourceDictionary> |
ラベルや目盛など他のコントロールも同時に配置していますが,
今回のメインは GraphGridPath という名前を付けた Path オブジェクトです.
LineGraph.cs の静的なコンストラクタ内で
DefaultStyleKeyProperty.OverrideMetadata() メソッドをコールしているため,
Generic.xaml で定義した名前を FindName() メソッドで探し出すことができます.
FindName() での検索先が更新されるタイミングは OnApplyTemplate() イベントハンドラが最速らしいので,
このメソッドを次のように override します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
#region TemplatePart private const string PART_MainContainerGrid = "MainContainerGrid"; private const string PART_TitleTextBlock = "TitleTextBlock"; private const string PART_XLabelTextBlock = "XLabelTextBlock"; private const string PART_YLabelTextBlock = "YLabelTextBlock"; private const string PART_Y2LabelTextBlock = "Y2LabelTextBlock"; private const string PART_XAxisItemsControl = "XAxisItemsControl"; private const string PART_YAxisItemsControl = "YAxisItemsControl"; private const string PART_Y2AxisItemsControl = "Y2AxisItemsControl"; private const string PART_GraphItemsControl = "GraphItemsControl"; private const string PART_GraphCanvas = "GraphCanvas"; private const string PART_GraphGridPath = "GraphGridPath"; #endregion #region FrameworkElement プライベートプロパティ private Grid mainContainerGrid; /// <summary> /// メインコンテナの Grid /// </summary> private Grid MainContainerGrid { get { return mainContainerGrid; } set { // イベントハンドラ登録抹消 if (mainContainerGrid != null) mainContainerGrid.SizeChanged -= mainContainerGrid_SizeChanged; mainContainerGrid = value; // イベントハンドラ登録 if (mainContainerGrid != null) mainContainerGrid.SizeChanged += mainContainerGrid_SizeChanged; } } private TextBlock titleTextBlock; /// <summary> /// グラフタイトルの TextBlock /// </summary> private TextBlock TitleTextBlock { get { return titleTextBlock; } set { titleTextBlock = value; } } private TextBlock xLabelTextBlock; /// <summary> /// 横軸ラベルの TextBlock /// </summary> private TextBlock XLabelTextBlock { get { return xLabelTextBlock; } set { xLabelTextBlock = value; } } private TextBlock yLabelTextBlock; /// <summary> /// 縦軸ラベルの TextBlock /// </summary> private TextBlock YLabelTextBlock { get { return yLabelTextBlock; } set { yLabelTextBlock = value; } } private TextBlock y2LabelTextBlock; /// <summary> /// 第 2 主軸ラベルの TextBlock /// </summary> private TextBlock Y2LabelTextBlock { get { return y2LabelTextBlock; } set { y2LabelTextBlock = value; } } private ItemsControl xAxisItemsControl; /// <summary> /// 横軸目盛の ItemsControl /// </summary> private ItemsControl XAxisItemsControl { get { return xAxisItemsControl; } set { xAxisItemsControl = value; } } private ItemsControl yAxisItemsControl; /// <summary> /// 縦軸目盛の ItemsControl /// </summary> private ItemsControl YAxisItemsControl { get { return yAxisItemsControl; } set { yAxisItemsControl = value; } } private ItemsControl y2AxisItemsControl; /// <summary> /// 第 2 主軸目盛の ItemsControl /// </summary> private ItemsControl Y2AxisItemsControl { get { return y2AxisItemsControl; } set { y2AxisItemsControl = value; } } private ItemsControl graphItemsControl; /// <summary> /// グラフデータの ItemsControl /// </summary> private ItemsControl GraphItemsControl { get { return graphItemsControl; } set { graphItemsControl = value; } } private Canvas graphCanvas; /// <summary> /// グラフデータの Canvas /// </summary> private Canvas GraphCanvas { get { return graphCanvas; } set { graphCanvas = value; } } private Path graphGridPath; /// <summary> /// グラフのグリッド線 /// </summary> public Path GraphGridPath { get { return graphGridPath; } set { graphGridPath = value; } } #endregion #region プライベートプロパティ private bool isApplyTemplate; /// <summary> /// テンプレート適用済かどうか確認する /// </summary> private bool IsApplyTemplate { get { return isApplyTemplate; } set { isApplyTemplate = value; } } #endregion #region イベントハンドラ /// <summary> /// テンプレート適用後の処理 /// </summary> public override void OnApplyTemplate() { base.OnApplyTemplate(); MainContainerGrid = this.Template.FindName(PART_MainContainerGrid, this) as Grid; TitleTextBlock = this.Template.FindName(PART_TitleTextBlock, this) as TextBlock; XLabelTextBlock = this.Template.FindName(PART_XLabelTextBlock, this) as TextBlock; YLabelTextBlock = this.Template.FindName(PART_YLabelTextBlock, this) as TextBlock; Y2LabelTextBlock = this.Template.FindName(PART_Y2LabelTextBlock, this) as TextBlock; XAxisItemsControl = this.Template.FindName(PART_XAxisItemsControl, this) as ItemsControl; YAxisItemsControl = this.Template.FindName(PART_YAxisItemsControl, this) as ItemsControl; GraphItemsControl = this.Template.FindName(PART_GraphItemsControl, this) as ItemsControl; GraphCanvas = this.Template.FindName(PART_GraphCanvas, this) as Canvas; GraphGridPath = this.Template.FindName(PART_GraphGridPath, this) as Path; IsApplyTemplate = true; } /// <summary> /// サイズ変更イベント処理 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void mainContainerGrid_SizeChanged(object sender, SizeChangedEventArgs e) { BuildGraph(); } #endregion #region 描画メソッド /// <summary> /// すべてのグラフ要素を更新する /// </summary> private void BuildGraph() { BuildGraphGrid(); } /// <summary> /// グラフ領域のグリッド線を描画する /// </summary> private void BuildGraphGrid() { /// <summary> /// グラフ領域のグリッド線を描画する /// </summary> private void BuildGraphGrid() { #endregion |
なんだかコードがいっぱいになってしまいました.
ついでに定義したコントロールもすべて読み込むようにしています.
ポイントは次の 2 点のみ.
- IsApplyTemplate プロパティでテンプレート適用済確認できるようにした
- MainContainerGrid のサイズ変更イベントハンドラでグラフ要素を更新するようにした
IsApplyTemplate というプライベートプロパティを定義し,
この OnApplyTemplate() を通ったかどうか確認できるようにしておきます.
これは,
依存関係プロパティのプロパティ変更のタイミングが,
OnApplyTemplate() が呼び出されるタイミングより速いため,
プロパティ変更のタイミングで XAML で定義したコントロールに対する操作を
おこなうときに NullReferenceException が発生しないようにするためのものです.
というわけで IsApplyTemplate プロパティは後で出てきます.
すべてを包括する MainContainerGrid にサイズ変更があった場合,
必ずグラフ要素を更新しなければならないため,
SizeChanged イベントハンドラを override しています.
ここまできてようやくグリッド線の描画にたどり着きました.
長かった….
BuildGraphGrid() メソッド内でグリッド線の描画に関するコードを記述します.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
/// <summary> /// グラフ領域のグリッド線を描画する /// </summary> private void BuildGraphGrid() { if (!IsApplyTemplate) return; if (XAxis == null) return; if (YAxis == null) return; if (Y2Axis == null) return; GeometryGroup geometryGroup = new GeometryGroup(); double value; var xmin = XAxisPositionFromValue(XAxis.Min); var xmax = XAxisPositionFromValue(XAxis.Max); var ymin = YAxisPositionFromValue(YAxis.Min); var ymax = YAxisPositionFromValue(YAxis.Max); // 横軸目盛線 value = XAxis.Min + XAxis.Step; while (value < XAxis.Max) { var pos = XAxisPositionFromValue(value); geometryGroup.Children.Add(new LineGeometry(new Point(pos, ymin), new Point(pos, ymax))); value += XAxis.Step; } // 縦軸目盛線 value = YAxis.Min + YAxis.Step; while (value < YAxis.Max) { var pos = YAxisPositionFromValue(value); geometryGroup.Children.Add(new LineGeometry(new Point(xmin, pos), new Point(xmax, pos))); value += YAxis.Step; } // 第 2 主軸目盛線 var y2min = Y2AxisPositionFromValue(Y2Axis.Min); var y2max = Y2AxisPositionFromValue(Y2Axis.Max); value = Y2Axis.Min + Y2Axis.Step; while (value < Y2Axis.Max) { var pos = Y2AxisPositionFromValue(value); geometryGroup.Children.Add(new LineGeometry(new Point(xmin, pos), new Point(xmax, pos))); value += Y2Axis.Step; } // GeometryGroup を変更不可にする geometryGroup.Freeze(); // GraphGridPath を更新する GraphGridPath.Data = geometryGroup; } |
GeometryGroup を新規に生成して,
それぞれの軸に対するグリッド線を Children に追加しています.
ここに出てくる XAxisPositionFromValue() メソッドなどは,
描画すべき位置をコントロールのサイズと軸設定から算出してくれるヘルパで,
次のように定義しています.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
#region 描画メソッドヘルパ /// <summary> /// 横軸の [pixel/value] /// </summary> /// <returns></returns> private double XAxisPxParValue() { if (graphCanvas == null) return 0.0; else if (XAxis == null) return 0.0; else return XAxis.Width == 0 ? 0.0 : graphCanvas.RenderSize.Width / XAxis.Width; } /// <summary> /// 縦軸の [pixel/value] /// </summary> /// <returns></returns> private double YAxisPxParValue() { if (graphCanvas == null) return 0.0; else if (YAxis == null) return 0.0; else return YAxis.Width == 0 ? 0.0 : graphCanvas.RenderSize.Height / YAxis.Width; } /// <summary> /// 第 2 主軸の [pixel/value] /// </summary> /// <returns></returns> private double Y2AxisPxParValue() { if (graphCanvas == null) return 0.0; else if (Y2Axis == null) return 0.0; else return Y2Axis.Width == 0 ? 0.0 : graphCanvas.RenderSize.Height / Y2Axis.Width; } /// <summary> /// 横軸のピクセル位置を算出する /// </summary> /// <param name="value">横軸の目盛上の値</param> /// <returns></returns> private double XAxisPositionFromValue(double value) { return XAxis == null ? 0.0 : (value - XAxis.Min) * XAxisPxParValue(); } /// <summary> /// 縦軸のピクセル位置を算出する /// </summary> /// <param name="value">縦軸の目盛上の値</param> /// <returns></returns> private double YAxisPositionFromValue(double value) { if (graphCanvas == null) return 0.0; else if (YAxis == null) return 0.0; else return graphCanvas.RenderSize.Height - (value - YAxis.Min) * YAxisPxParValue(); } /// <summary> /// 第 2 主軸のピクセル位置を算出する /// </summary> /// <param name="value">第 2 主軸の目盛上の値</param> /// <returns></returns> private double Y2AxisPositionFromValue(double value) { if (graphCanvas == null) return 0.0; else if (Y2Axis == null) return 0.0; else return graphCanvas.RenderSize.Height - (value - Y2Axis.Min) * Y2AxisPxParValue(); } #endregion |
以上.これでグリッド線が描画されます.
試しに見てみるには,
WPF アプリケーションプロジェクトのほうで
実際に View に配置してみます.
MainView.xaml はこんな感じ.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<Window x:Class="CustomControlsSample.View.MainView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:vm="clr-namespace:CustomControlsSample.ViewModel" xmlns:custom="clr-namespace:CustomControls;assembly=CustomControls" Title="MainView" Height="480" Width="640" > <Window.DataContext> <vm:MainViewModel /> </Window.DataContext> <Grid> <custom:LineGraph BorderBrush="Black" BorderThickness="1" XAxis="{Binding XAxis}" YAxis="{Binding YAxis}" Y2Axis="{Binding Y2Axis}" /> </Grid> </Window> |
対する MainViewModel は次のようになります.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
namespace CustomControlsSample.ViewModel { using CustomControls; using System.Collections.ObjectModel; using System.Collections.Generic; public class MainViewModel : ViewModelBase { /// <summary> /// コンストラクタ /// </summary> public MainViewModel() { } private AxisStyle xAxis = new AxisStyle(); /// <summary> /// 横軸目盛設定 /// </summary> public AxisStyle XAxis { get { return xAxis; } set { SetProperty(ref xAxis, value, "XAxis"); } } private AxisStyle yAxis = new AxisStyle(); /// <summary> /// 縦軸目盛設定 /// </summary> public AxisStyle YAxis { get { return yAxis; } set { SetProperty(ref yAxis, value, "YAxis"); } } private AxisStyle y2Axis = new AxisStyle(); /// <summary> /// 第 2 主軸目盛設定 /// </summary> public AxisStyle Y2Axis { get { return y2Axis; } set { SetProperty(ref y2Axis, value, "Y2Axis"); } } } } |
ちなみに ViewModelBase は INotifyPropertyChanged を実装した
NotifyPropertyChangedDataClass 抽象クラスと同じ内容です.
したがってここでも SetProperty() メソッドを使っています.
このままでは縦軸と第 2 主軸のグリッド線の見分けがつかないので
少し工夫しないといけませんが,
とりあえず基礎は完成.
なんか周辺要素が多くて予想以上に長くなった….