条形图中矩形的自动化 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();
    }
}