パレート図のようなグラフを表示する WPF コンポーネントをコーディングしています。正常に動作していますが、かなりがらくただと感じています。理由は次のとおりです。
非常に多くのコンテナーを使用しています。単純なチャートの場合、おそらく約 50 個のコンテナーが構造化されています。
余白 (ViewModel に格納されている) を使用して適切な場所に四角形を配置しました。
コンポーネントを適切な場所に配置し、それに応じてスケーリングするには、ViewModel 内のグラフィック コンポーネントのサイズを知る必要があります。
2 つのレイヤーを使用してチャートをレンダリングしています。1 つはチャート用、もう 1 つはチャートのスケールを表示するためです。これはまったく良くないと思います
どのように見えるか
http://hpics.li/fd2b0bd (初心者のため画像を表示できません)
ビューモデル
一番上のオブジェクトはParetoChartVMで、SerieVM の ObservableCollection と AxisVM の別の 1 つ、Title とチャートの現在のサイズを含みます。
SerieVMは、ValuePointVMのOservableCollection で構成されます (グラフの四角形を表します)。
ValuePointVMには、Brush、数値、幅と高さ、および余白 (Thickness オブジェクト) が含まれています。
AxisVMには、ScaleVM のMinimumValue、MaximumValue、NumberOfScales、および ObservableCollection が含まれています。
ScaleVMには、Value、 ValuePercentage (上部に値が表示され、下部に最大値のパーセンテージが表示されます)、TopMargin および BottomMargin (両方とも Thickness オブジェクト) が含まれます。
意見
ビュー レイヤーには、ParetoChartV WPF コンポーネントのみが含まれます。このコンポーネントには ParetoChartVM のみが含まれ、彼の DataContext はこの ParetoChartVM に設定されます。
使い方
グラフ コンテナーのサイズが変更されるたびに、ParetoChartVM に通知します。これにより、すべての位置/幅/高さが再計算され、それらのプロパティのバインディングを使用してインターフェイスが更新されます。
これが XAML です (かなり大きいです)。
<UserControl x:Class="ParetoChart.View.ParetoChartV"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:viewModel="clr-namespace:ParetoChart.ViewModel"
xmlns:converter="clr-namespace:ParetoChart.ViewModel.Converter"
DataContext="{Binding RelativeSource={RelativeSource self}, Path=ParetoChart}">
<Grid>
<Grid.Resources>
<Style x:Key="TitleTextStyle" TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="20"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
</Style>
</Grid.Resources>
<!--
Fours parts: Title, Scales, Chart, Caption
Scales & Chart have the same location, the Scales layer is an overlay on the chart layer
-->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width=".5*"/>
<ColumnDefinition Width=".5*"/>
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
<!--Title-->
<TextBlock Style="{StaticResource TitleTextStyle}" Text="{Binding Title, Mode=OneWay}"/>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Margin="0,20,0,0"> <!--Chart layer-->
<Grid.Background>
<ImageBrush ImageSource="../Resources/Images/GlassBlock.png"/>
</Grid.Background>
<Grid SizeChanged="FrameworkElement_OnSizeChanged" Margin="10, 0">
<ItemsControl ItemsSource="{Binding Series, Mode=OneWay}"> <!-- Container for SerieVM -> for each "line" on the chart -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SerieVM}">
<ItemsControl ItemsSource="{Binding Values, Mode=OneWay}"> <!--Container for ValuePoints -> each rectangle -->
<ItemsControl.Resources>
<converter:SolidColorToGradientColor x:Key="SolidColorToGradientColor"/>
</ItemsControl.Resources>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:IValuePoint}">
<Rectangle Width="{Binding RectangleWidth, Mode=OneWay}"
Height="{Binding RectangleHeight, Mode=OneWay}"
Fill="{Binding BrushColor, Converter={StaticResource SolidColorToGradientColor}, Mode=OneWay}"
Margin="{Binding Margins, Mode=OneWay}">
<Rectangle.Effect>
<DropShadowEffect Color="Gray"/>
</Rectangle.Effect>
</Rectangle>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" Margin="0,0,0,0"> <!--Scales layer-->
<ItemsControl ItemsSource="{Binding Axes, Mode=OneWay}"> <!-- Container containing axes -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:AxisVM}">
<ItemsControl ItemsSource="{Binding Scales, Mode=OneWay}"> <!-- Container containing scales -->
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:ScaleVM}">
<Canvas>
<Canvas.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15"/>
<Setter Property="TextAlignment" Value="Center"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="#0C077D"/>
</Style>
</Canvas.Resources>
<TextBlock x:Name="MyTB2" Text="{Binding Value, StringFormat={}{0:N0}}"
Margin="{Binding TopCaptionMargins, Mode=OneWay}"/> <!--Scale point value-->
<Line X2="{Binding TopCaptionMargins.Left, Mode=OneWay}"
Y1="20"
Y2="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Grid}}, Path=ActualHeight, Mode=OneWay}"
X1="{Binding TopCaptionMargins.Left, Mode=OneWay}"
StrokeDashArray="1 2" Stroke="Gray"/> <!-- vertical dashed line at the same X location of the scale -->
<TextBlock Text="{Binding ValuePercentage, StringFormat={}{0:N0}%, Mode=OneWay}"
Margin="{Binding BottomCaptionMargins, Mode=OneWay}"/><!--Scale point percentage of maximum-->
</Canvas>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
<!-- This part is probably ok -->
<Grid Grid.Row="1" Grid.Column="1" Margin="10,20,0,0"> <!--Caption-->
<Grid.Background>
<ImageBrush ImageSource="../Resources/Images/GlassBlock.png"/>
</Grid.Background>
<ItemsControl ItemsSource="{Binding Series, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:SerieVM}">
<ItemsControl ItemsSource="{Binding Values, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type viewModel:IValuePoint}">
<Grid Margin="20, 20, 10, 20">
<Grid.Resources>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="FontSize" Value="15"/>
<Setter Property="VerticalAlignment" Value="Center"/>
<Setter Property="Foreground" Value="#0C077D"/>
</Style>
</Grid.Resources>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="70"/>
<ColumnDefinition Width=".8*"/>
</Grid.ColumnDefinitions>
<Ellipse Grid.Column="0" Width="30" Height="30" Fill="{Binding BrushColor, Mode=OneWay}"/>
<TextBlock Margin="20,0,0,0" Grid.Column="1" Text="{Binding ValueOfXAxis, StringFormat={}{0:N0}, Mode=OneWay}"
HorizontalAlignment="Stretch" TextAlignment="Right"/>
<TextBlock Margin="20,0,0,0" Grid.Column="2" Text="{Binding ValueDescription, Mode=OneWay}" TextWrapping="WrapWithOverflow"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Vertical"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</Grid>
</UserControl>
したがって、ObservableCollection ごとに、それらを格納および表示する ItemsControl を作成します。また、ItemsControl.ItemsPanel に Canvas を配置して、すべてのコンポーネントを必要な場所に Margins で配置する必要があります。これらのアイテムは ObservableCollection にも含まれているため、キャンバスを ItemsPanel として ItemsControl にも配置する必要があります。
私のコード構造に問題があると思いますか? 私はWPFとMVVMのパターンを始めているので、いくつか見たら教えてください。できるだけ詳しく説明してください。
(ドットネット フレームワーク バージョン 3.5 を使用しているため、コンテナーの SizeChanged イベントに対話性を使用できませんでした)
あなたの助けとあなたの時間をありがとう
編集(関連する問題)
私が抱えている副次的な問題は、テキストブロックを特定のポイントの中央に配置するために行ったコンバーターです (チャートの垂直の破線でスケール値を示すテキストを中央に配置します)。
これが私がやった方法です:
using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
namespace ParetoChart.ViewModel.Converter {
public class CenterTextblockTextConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
if (values[0] == DependencyProperty.UnsetValue || values[1] == DependencyProperty.UnsetValue) {
return DependencyProperty.UnsetValue;
}
if (values[0] is Thickness) {
Thickness margins = (Thickness) values[0];
double width = (double) values[1];
margins.Left = margins.Left - (width / 2.0);
return margins;
}
return DependencyProperty.UnsetValue;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
throw new NotImplementedException();
}
}
}
XAML で、Textblocks を次のように変更しました。
<TextBlock Text="{Binding ValuePercentage, StringFormat={}{0:N0}%, Mode=OneWay}"
x:Name="MyTB">
<TextBlock.Margin>
<MultiBinding Converter="{StaticResource CenterTextblockTextConverter}">
<Binding Path="BottomCaptionMargins"/>
<Binding ElementName="MyTB" Path="ActualWidth"/>
</MultiBinding>
</TextBlock.Margin>
</TextBlock>
そのため、名前を付けて複数の値をコンバーターに渡します。
したがって、ストレスがかかると、このコンバーターはコンポーネントを狂わせ、コンバーターは1秒間に約10000回呼び出され、一見無限ループになることを除いて機能しています。その後、グラフのサイズは変更されません (ただし、ウィンドウ内の他のコンポーネントは引き続きサイズ変更に応答しています)。私が提供したスクリーンショットは、このコンバーターを使用しているものであることに注意してください。この問題のために使用を中止しました。
なぜこれが起こるのかについて考えがありますか?
編集n°2(サイドの問題について)
いくつかのテストを行ったところ、コンバーターの問題は、コンバーターへの ActualWidth パラメーターで発生するようです。Textblock は浮動小数点に問題があるようです。実際、変更していない幅が突然 ~8.08 から ~28.449 に変わります。次のスクリーンショットは、この値を示しています。
(左の値はコンバーターの呼び出し回数、右の値はパラメーターとして渡される実際の幅です)
ActualWidth 値は 28.44999999... と 28.45 の間で変化します。これにより、コンバーターが毎回トリガーされ、チャートが狂ってしまいます。
それを修正する方法はありますか?(私は幅が突然ジャンプする理由を理解しようとしています.
編集 n°3 (側面の問題について)
マージンがテキストブロックの幅を変更できるかどうかを確認しましたが、左マージンと上マージンのみが変更され、下マージンと右マージンは変更されません。xaml で、Margin から Canvas.Left および Canvas.Top へのバインディングを次のように変更しました。
<TextBlock Text="{Binding ValuePercentage, StringFormat={}{0:N0}%, Mode=OneWay}"
x:Name="MyTB" MaxWidth="40">
<Canvas.Left>
<MultiBinding Converter="{StaticResource CenterTextblockTextConverter}" Mode="OneWay">
<Binding Path="BottomCaptionMargins.Left"/>
<Binding ElementName="MyTB" Path="ActualWidth" Mode="OneWay"/>
</MultiBinding>
</Canvas.Left>
<Canvas.Top>
<Binding Path="BottomCaptionMargins.Top"/>
</Canvas.Top>
</TextBlock>
その後、バグはなくなり、Textblock の幅が変更されなくなり、このバグが発生しました。問題は解決しましたが、まだ理由がわかりません。