【问题标题】:Automating ScaleTransform for Rectangles in bar chart为条形图中的矩形自动化 ScaleTransform
【发布时间】:2019-12-07 14:22:02
【问题描述】:

我有这个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 中使用过它,它通常看起来像这样:

这些行顶部的值在ICommand 中为SizeChanged 事件重新计算。当我调整窗口大小时,它变成这样:

矩形的高度不会自动改变!我可以重新计算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();
    }
}

当我调整窗口大小时仍然存在一个小问题,这可能是因为我已经硬编码了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);
}

解决了调整大小的问题。

【问题讨论】:

    标签: wpf charts itemscontrol


    【解决方案1】:

    作为我对您的other question 的回答的扩展,当值范围为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());
    

    编辑:您也可以对 Rectangle 的 Height 属性使用 MultiBinding,而不是 ScaleTransform。下面的例子假设除了 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();
        }
    }
    

    【讨论】:

    • 由于某种原因无法正常工作,请参阅 EDIT 部分
    • 看起来您还没有删除网格的固定高度,在另一个答案中是 100。
    • 另请注意,此临时方法仅适用于 0 到 1 范围内的值。如果您需要另一个值范围,请将 ScaleY 属性与适当的值转换器绑定。
    • 不,ScaleY 绑定中的适当值转换器应该可以工作。假设您的值范围是 0..100 而不是 0..1,转换器将返回 ActualWidth 除以 100。即Convert 方法只需执行return (double)value / 100;
    • 实际范围可以是转换器的属性,也可以通过 ConverterParameter 传递。如果它动态变化,请使用带有 IMultiValueConverter 的 MultiBinding。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-22
    • 1970-01-01
    • 2021-09-08
    • 1970-01-01
    • 2013-02-23
    • 1970-01-01
    • 2022-12-03
    相关资源
    最近更新 更多