Reputation:
I've this UserControl
for the bar chart:
<UserControl ... Name="uc">
<Grid>
<Canvas>
<Canvas.Resources>
<Style TargetType="Line">
<Setter Property="X1" Value="0"/>
<Setter Property="X2" Value="{Binding ActualWidth, ElementName=uc}"/>
<Setter Property="StrokeThickness" Value="1"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="Canvas.Right" Value="0"/>
</Style>
</Canvas.Resources>
<Line Y1="{Binding HighestPoint}" Y2="{Binding HighestPoint}"
Canvas.Bottom="{Binding HighestPoint}"
Stroke="Red"/>
<TextBlock Text="{Binding HighestPoint, StringFormat=N0}"
Canvas.Bottom="{Binding HighestPoint}"/>
<Line Y1="{Binding SecondPoint}" Y2="{Binding SecondPoint}" Stroke="Blue"
Canvas.Bottom="{Binding SecondPoint}"/>
<TextBlock Text="{Binding SecondPoint, StringFormat=N0}"
Canvas.Bottom="{Binding SecondPoint}"/>
<Line Y1="{Binding FirstPoint}" Y2="{Binding FirstPoint}" Stroke="Green"
Canvas.Bottom="{Binding FirstPoint}"/>
<TextBlock Text="{Binding FirstPoint, StringFormat=N0}"
Canvas.Bottom="{Binding FirstPoint}"/>
</Canvas>
<ItemsControl ScrollViewer.CanContentScroll="True"
ItemsSource="{Binding RectCollection}"
Margin="0 0 20 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="20" Height="{Binding}"
Margin="0 0 2 0"
VerticalAlignment="Bottom"
Opacity=".5" Fill="Green"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer
VerticalScrollBarVisibility="Hidden"
Background="{TemplateBinding Panel.Background}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
On my MainWindow
I've used it in a Grid.Row
and it it looks like this normally:
Values on top of those lines are recalculated in an ICommand
for SizeChanged
event. When I resize the window it becomes like this:
Height of the rectangles don't change automatically! I could recalculate the height, which is a double
, of each rectangle in that ICommand
to readjust BUT that's inefficient, right? Is there any simple way to transform all those rectangles in a single shot?
EDIT
I'd to change a few things in Canvas
as well to make it work. Here's what I've in UserControl
now:
<UserControl .." Name="uc">
<UserControl.Resources>
<local:MyConverter x:Key="converter"/>
</UserControl.Resources>
<Grid VerticalAlignment="Bottom">
<Canvas>
<Canvas.Resources>
<Style TargetType="Line">
<Setter Property="X1" Value="0"/>
<Setter Property="X2" Value="{Binding ActualWidth, ElementName=uc}"/>
<Setter Property="StrokeThickness" Value="1"/>
</Style>
<Style TargetType="TextBlock">
<Setter Property="Canvas.Right" Value="0"/>
</Style>
</Canvas.Resources>
<Line Y1="135" Y2="135" Canvas.Bottom="135" Stroke="Red"/>
<TextBlock Text="{Binding HighestPoint, StringFormat=N0}" Canvas.Bottom="135"/>
<Line Y1="90" Y2="90" Stroke="Blue" Canvas.Bottom="90"/>
<TextBlock Text="{Binding SecondPoint, StringFormat=N0}" Canvas.Bottom="90"/>
<Line Y1="45" Y2="45" Stroke="Green" Canvas.Bottom="45"/>
<TextBlock Text="{Binding FirstPoint, StringFormat=N0}" Canvas.Bottom="45"/>
</Canvas>
<ItemsControl ScrollViewer.CanContentScroll="True"
Height="135"
ItemsSource="{Binding RectCollection}"
Margin="0 0 20 0">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid>
<Rectangle Width="20"
Margin="0 0 2 0"
VerticalAlignment="Bottom"
Opacity=".5" Fill="Green">
<Rectangle.Height>
<MultiBinding Converter="{StaticResource converter}">
<Binding Path="ActualHeight"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="DataContext.HighestPoint"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding />
</MultiBinding>
</Rectangle.Height>
</Rectangle>
<TextBlock Text="{Binding StringFormat=N2}"
Margin="0 0 0 20">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer
VerticalScrollBarVisibility="Hidden"
Background="{TemplateBinding Panel.Background}">
<ItemsPresenter SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
</UserControl>
and in MainWindow
I've these:
<Window ...>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding SizeChanged}"
CommandParameter="{Binding ElementName=uc}"/>
</i:EventTrigger>
<i:EventTrigger EventName="SizeChanged">
<i:InvokeCommandAction Command="{Binding SizeChanged}"
CommandParameter="{Binding ElementName=uc}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
<Window.DataContext>
<local:VM/>
</Window.DataContext>
<Grid Margin="5">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<local:TestControl Grid.Row="1" x:Name="uc"/>
<Button Grid.Row="2" Content="Add" Margin="50" Command="{Binding Add}"/>
</Grid>
</Window>
and in ViewModel These are the relevant functions that adds rectangles and sets range:
public VM()
{
Add = new Command(AddRect, (o) => true);
SizeChanged = new Command(sizeChanged, (o) => true);
}
void sizeChanged(object obj)
{
var c = obj as TestControl;
CalculateLineText(c.ActualHeight);
}
void AddRect(object obj)
{
var value = rand.NextDouble() * 500;
if (max < value) CalculateLineText(value);
RectCollection.Insert(0, value);
}
void CalculateLineText(double d)
{
max = d;
HighestPoint = Math.Round(d);
FirstPoint = Math.Round(d / 3);
SecondPoint = Math.Round(d / 3 * 2);
}
and in value converter:
public class MyConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var height = (double)values[0];
var highest = (double)values[1];
var value = (double)values[2];
return value * height / highest;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
There's still a little issue when I resize the window and it probably because I've hardcoded the height of the ItemsControl
.
EDIT
This:
void CalculateLineText(double d)
{
if (max < d) max = d;
HighestPoint = Math.Round(max);
FirstPoint = Math.Round(max / 3);
SecondPoint = Math.Round(max / 3 * 2);
}
solves the issue on resize.
Upvotes: 0
Views: 164
Reputation: 128067
As an extension of my answer to your other question, you could use the ActualHeight of the ItemsControl as a scale factor when the value range is 0..1
:
<ItemsControl ItemsSource="{Binding}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="20" Margin="2">
<Rectangle VerticalAlignment="Bottom"
Height="{Binding}" Fill="LightGray">
<Rectangle.LayoutTransform>
<ScaleTransform ScaleY="{Binding ActualHeight,
RelativeSource={RelativeSource AncestorType=ItemsControl}}"/>
</Rectangle.LayoutTransform>
</Rectangle>
<TextBlock Text="{Binding StringFormat={}{0:N2}}">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
with this in the code behind:
Random r = new Random();
DataContext = Enumerable.Range(0, 20).Select(i => r.NextDouble());
Edit: Instead of a ScaleTransform you may also use a MultiBinding for the Rectangle's Height property. The following example assumes that besides a Values
collection there is also a Range
property in the view model, e.g. like this:
Random r = new Random();
DataContext = new
{
Range = 100d,
Values = Enumerable.Range(0, 20).Select(i => r.NextDouble() * 100)
};
The XAML would then look like this:
<ItemsControl ItemsSource="{Binding Values}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="20" Margin="2">
<Rectangle VerticalAlignment="Bottom" Fill="LightGray">
<Rectangle.Height>
<MultiBinding Converter="{StaticResource HeightConverter}">
<Binding Path="ActualHeight"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="DataContext.Range"
RelativeSource="{RelativeSource AncestorType=ItemsControl}"/>
<Binding Path="."/>
</MultiBinding>
</Rectangle.Height>
</Rectangle>
<TextBlock Text="{Binding StringFormat={}{0:N2}}">
<TextBlock.LayoutTransform>
<RotateTransform Angle="-90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
with this IValueConverter:
public class HeightConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
return (double)values[0] / (double)values[1] * (double)values[2];
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotSupportedException();
}
}
Upvotes: 0