条形图中矩形的自动化 ScaleTransform
Automating ScaleTransform for Rectangles in bar chart
我有这个 UserControl
条形图:
<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>
在我的 MainWindow
上,我在 Grid.Row
中使用过它,它通常看起来像这样:
这些行顶部的值在 SizeChanged
事件的 ICommand
中重新计算。当我调整 window 的大小时,它变成这样:
矩形的高度不会自动改变!我可以重新计算 ICommand
中每个矩形的高度 double
来重新调整,但效率很低,对吧?有什么简单的方法可以一次转换所有这些矩形吗?
编辑
我还要更改 Canvas
中的一些内容以使其正常工作。这是我现在 UserControl
中的内容:
<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>
在 MainWindow
我有这些:
<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>
和ViewModel中添加矩形和设置范围的相关函数:
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);
}
在值转换器中:
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();
}
}
当我调整 window 的大小时仍然有一个小问题,这可能是因为我已经硬编码了 ItemsControl
的高度。
编辑
这个:
void CalculateLineText(double d)
{
if (max < d) max = d;
HighestPoint = Math.Round(max);
FirstPoint = Math.Round(max / 3);
SecondPoint = Math.Round(max / 3 * 2);
}
解决了调整大小的问题。
作为我对您的 回答的扩展,当值范围为 0..1
:
时,您可以使用 ItemsControl 的 ActualHeight 作为比例因子
<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>
在后面的代码中:
Random r = new Random();
DataContext = Enumerable.Range(0, 20).Select(i => r.NextDouble());
编辑:除了 ScaleTransform,您还可以为矩形的高度使用 MultiBinding 属性。下面的示例假设除了 Values
集合之外,视图模型中还有一个 Range
属性,例如像这样:
Random r = new Random();
DataContext = new
{
Range = 100d,
Values = Enumerable.Range(0, 20).Select(i => r.NextDouble() * 100)
};
XAML 看起来像这样:
<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>
使用此 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();
}
}
我有这个 UserControl
条形图:
<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>
在我的 MainWindow
上,我在 Grid.Row
中使用过它,它通常看起来像这样:
这些行顶部的值在 SizeChanged
事件的 ICommand
中重新计算。当我调整 window 的大小时,它变成这样:
矩形的高度不会自动改变!我可以重新计算 ICommand
中每个矩形的高度 double
来重新调整,但效率很低,对吧?有什么简单的方法可以一次转换所有这些矩形吗?
编辑
我还要更改 Canvas
中的一些内容以使其正常工作。这是我现在 UserControl
中的内容:
<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>
在 MainWindow
我有这些:
<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>
和ViewModel中添加矩形和设置范围的相关函数:
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);
}
在值转换器中:
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();
}
}
当我调整 window 的大小时仍然有一个小问题,这可能是因为我已经硬编码了 ItemsControl
的高度。
编辑
这个:
void CalculateLineText(double d)
{
if (max < d) max = d;
HighestPoint = Math.Round(max);
FirstPoint = Math.Round(max / 3);
SecondPoint = Math.Round(max / 3 * 2);
}
解决了调整大小的问题。
作为我对您的 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>
在后面的代码中:
Random r = new Random();
DataContext = Enumerable.Range(0, 20).Select(i => r.NextDouble());
编辑:除了 ScaleTransform,您还可以为矩形的高度使用 MultiBinding 属性。下面的示例假设除了 Values
集合之外,视图模型中还有一个 Range
属性,例如像这样:
Random r = new Random();
DataContext = new
{
Range = 100d,
Values = Enumerable.Range(0, 20).Select(i => r.NextDouble() * 100)
};
XAML 看起来像这样:
<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>
使用此 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();
}
}