I'm working on a large code project that was written by someone else, so I'm trying to add a change with as little impact as possible. It uses System.Windows.Controls.DataVisualization.Toolkit
to create a linechart with a number of LineSeries
combined into one MultiChart
, which is a class my predecessor created himself.
The lines are all the same color. I was asked to make them different colors. Or more than one color really. The number of lines is not constant, so I was hoping to loop through a list of colors. Then all I would have to do is make a large enough to cover most cases.
Because of the inconsistency in line number, I can't explicitly define each color. In addition, the MultiChart
has a SeriesSource
property, which is an ObservableCollection
of multiple LineSeries
which is in turn an ObservableCollection
of data points, that is set via binding to the ViewModel.
So I think I'll have to bind the color to a value outside of the xaml. Since it's view related work, I imagine the code behind would be a valid location for my color changing, but I've been unable to find a solution that works there.
I've looked at a number of question on SO and elsewhere and none of them quite worked, with or without consideration for the MVVM pattern. I'm just looking for a working solution that I can rework into MVVM, but bonus points if it's good practice as well.
I have been working with WPF for a while now, and for some reason I can't fully wrap my head around data binding, which is likely why I've been unsuccessful with most of the solutions I found. Despite that I think I got close with this:
In <UserControl.Resources>
<local:LocalColorConverter x:Key="MyColorConverter"/>
<Style x:Key="dataPointStyle" TargetType="{x:Type charting:LineDataPoint}">
<Setter Property="Background" Value="{Binding Path=DataContext.ColorCount,
RelativeSource={RelativeSource AncestorType=local:MultiChart, Mode=FindAncestor},
Converter={StaticResource MyColorConverter}}"/>
</Style>
In LocalColorConverter.cs
:
class LocalColorConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int count = (int)value;
//SolidColorBrush returnBrush = new SolidColorBrush();
SolidColorBrush color = new SolidColorBrush();
switch (count % 5)
{
case 0:
color.Color = Colors.Blue;
break;
case 1:
color.Color = Colors.Green;
break;
case 2:
color.Color = Colors.Red;
break;
case 3:
color.Color = Colors.Purple;
break;
case 4:
color.Color = Colors.Yellow;
break;
}
return color;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
I realize this is not very good in practice. It relies on a ColorCount value from the viewmodel, which is information relevant to the view. Bad MVVM, but I'm just trying to get it working before I make it fit the pattern.
In addition, it doesn't really work as is, since ColorCount
is never changed. It's initially set to 0 in the viewmodel. If I were to continue down this path, I would like to increment the value of ColorCount so that the colors would change each time the Converter is used.
I tried just doing ((int)value)++
before the Converter returns, but that didn't work. I didn't think it would, but it gives you an idea of what I'm looking for.
Otherwise, I think my best solution is to do this operation in the code behind. But I don't know where/how to do it. It would have to change the color when the UserControl
gets to a new LineSeries
. The current code behind has a LineSeries_Loaded
event handler implemented, but the color isn't set in the line, it's set for each point in the line. So I need to increment my color counter at each line, then use the count at each point to determine it's color.
I've tried this, but I can't seem to find the Background property this way. Note that chart is the name given to the MultiChart
object.
private void LineSeries_Loaded(object sender, RoutedEventArgs e)
{
foreach(LineSeries line in chart.Series.Cast<LineSeries>())
{
foreach(LineDataPoint point in line)
{
}
}
}
But I get the error that line has no public GetEnumerator.
Any suggestions?
Well, looks like writing the question got my creative juices flowing. I remembered I tried to set the style setter in the code behind, but you can't modify the style at the loaded time. But I just realized I can switch styles at any time. So I made these:
<Style x:Key="blueDataPointStyle" TargetType="{x:Type charting:LineDataPoint}" BasedOn="{StaticResource dataPointStyle}">
<Setter Property="Background" Value="Blue"/>
</Style>
<Style x:Key="redDataPointStyle" TargetType="{x:Type charting:LineDataPoint}" BasedOn="{StaticResource dataPointStyle}">
<Setter Property="Background" Value="Red"/>
</Style>
<Style x:Key="greenDataPointStyle" TargetType="{x:Type charting:LineDataPoint}" BasedOn="{StaticResource dataPointStyle}">
<Setter Property="Background" Value="Green"/>
</Style>
Then, in the code behind:
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
int count = 0;
foreach (LineSeries line in chart.Series)
{
switch (count % 3)
{
case 0:
line.DataPointStyle = Resources["blueDataPointStyle"] as Style;
break;
case 1:
line.DataPointStyle = Resources["redDataPointStyle"] as Style;
break;
case 2:
line.DataPointStyle = Resources["greenDataPointStyle"] as Style;
break;
}
count++;
}
}
It's working! Well mostly, turns out every other line is grouped into one, so I still have some work to do. But this is the answer for what I asked. Plus it seems to fit MVVM. Turns out I don't need binding(probably why I was able to come up with it). But I am still interested in other ideas. I would like a proper binding example.
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.