如何解决WPF ItemControl慢速渲染
您好,我有一个WPF应用程序,其中有一个图像,用户可以在其中选择一个区域,一旦选择了该区域,就会在所选区域上方出现一个带有叉号的网格。
我来回应用一些变换,以便缩放和旋转网格以匹配图像坐标。
到目前为止,这种方法已经可以使用了,但是当有很多交叉时(〜+ 5k)UI冻结并花了一些时间来绘制十字。我已经应用了一些在Stackoverflow上找到的答案,例如虚拟化,ListView
,ListBox
,但我无法使其正常工作。我想知道是否有人可以在这里介绍一下,谢谢!!
编辑
所以我最终要完成所有相关的计算,以便在ViewModel上转换十字这并没有破坏MVVM模式,我使用AttachedProperties,它在ViewModel上为我提供了计算位置所需的数据。这是链接和说明-> https://stackoverflow.com/a/3667609/2315752
以下是主要代码:
MainWindow.ItemControl:
<ItemsControl ItemsSource="{Binding Crosses}">
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Canvas.Left"
Value="{Binding X}" />
<Setter Property="Canvas.Top"
Value="{Binding Y}" />
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Width="5"
Height="5"
StrokeThickness="1"
Stroke="1"
Style="{StaticResource ShapeCross}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
解决方法
关键是要在Canvas
上而不是项目上布置项目容器。这样,渲染将在面板的布局布局遍历期间发生。在渲染容器之后转换item元素(item容器的内容)会增加额外的渲染时间。
代替在Canvas
上平移点,您应该使用附加的属性Canvas.Left
和Canvas.Top
在Canvas
面板上布置项目容器。
诸如缩放之类的图形操作应直接在视图模型中对数据项集进行。要允许动态UI更新,请考虑实现自定义数据模型,该模型可实现INotifyPropertyChanged
,例如ObservablePoint
。
下面的示例绘制一个正弦图。该图由10,800个数据点组成。加载时间大约不到5秒,这是用于创建10,800 Point
实例的时间。
结果是即时渲染和相当流畅的滚动:
ViewModel.cs
class ViewModel
{
public ObservableCollection<Point> Points { get; set; }
public ViewModel()
{
this.Points = new ObservableCollection<Point>();
// Generate a sine graph of 10,800 points
// with an amplitude of 200px and a vertical offset of 200px
for (int x = 0; x < 360 * 30; x++)
{
var point = new Point()
{
X = x,Y = Math.Sin(x * Math.PI / 180) * 200 + 200};
}
this.Points.Add(point);
}
}
}
MainWindow.xaml
<Window>
<Window.DataContext>
<ViewModel />
</Window.DataContext>
<ListBox ItemsSource="{Binding Points}">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="11000" Height="500" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate DataType="Point">
<Grid>
<Line Stroke="Black" StrokeThickness="2" X1="0" X2="10" Y1="5" Y2="5" />
<Line Stroke="Black" StrokeThickness="2" X1="5" X2="5" Y1="0" Y2="10" />
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="ListBoxItem">
<Setter Property="Canvas.Left" Value="{Binding X}" />
<Setter Property="Canvas.Top" Value="{Binding Y}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</Window>
,
您可以创建一个用于计算比例并将其传递给ViewModel的类。
一个近似的实现及其用法。您可以创建一个类来计算比例并将其传递给ViewModel。
一个大概的实现及其使用。
public class ScaleCalcBinding : Freezable
{
public FrameworkElement SourceElement
{
get { return (FrameworkElement)GetValue(SourceElementProperty); }
set { SetValue(SourceElementProperty,value); }
}
// Using a DependencyProperty as the backing store for SourceElement. This enables animation,styling,binding,etc...
public static readonly DependencyProperty SourceElementProperty =
DependencyProperty.Register(nameof(SourceElement),typeof(FrameworkElement),typeof(ScaleCalcBinding),new PropertyMetadata(null,ElementChanged));
private static void ElementChanged(DependencyObject d,DependencyPropertyChangedEventArgs e)
{
ScaleCalcBinding dd = (ScaleCalcBinding)d;
FrameworkElement element = e.OldValue as FrameworkElement;
if (element != null)
element.SizeChanged -= dd.CalcScale;
element = e.NewValue as FrameworkElement;
if (element != null)
element.SizeChanged += dd.CalcScale;
dd.CalcScale();
}
private void CalcScale(object sender = null,SizeChangedEventArgs e = null)
{
if (SourceElement == null || TargetElement == null || ScanScale == null)
{
ScaleWidthResult = null;
ScaleHeightResult = null;
return;
}
ScaleWidthResult = SourceElement.ActualWidth / TargetElement.ActualWidth * ScanScale.Value;
ScaleHeightResult = SourceElement.ActualHeight / TargetElement.ActualHeight * ScanScale.Value;
}
public FrameworkElement TargetElement
{
get { return (FrameworkElement)GetValue(TargetElementProperty); }
set { SetValue(TargetElementProperty,value); }
}
// Using a DependencyProperty as the backing store for TargetElement. This enables animation,etc...
public static readonly DependencyProperty TargetElementProperty =
DependencyProperty.Register(nameof(TargetElement),new PropertyMetadata(null));
public double? ScanScale
{
get { return (double?)GetValue(ScanScaleProperty); }
set { SetValue(ScanScaleProperty,value); }
}
// Using a DependencyProperty as the backing store for ScanScale. This enables animation,etc...
public static readonly DependencyProperty ScanScaleProperty =
DependencyProperty.Register(nameof(ScanScale),typeof(double?),ElementChanged));
public double? ScaleWidthResult
{
get { return (double?)GetValue(ScaleResultWidthProperty); }
set { SetValue(ScaleResultWidthProperty,value); }
}
// Using a DependencyProperty as the backing store for ScaleWidthResult. This enables animation,etc...
public static readonly DependencyProperty ScaleResultWidthProperty =
DependencyProperty.Register(nameof(ScaleWidthResult),new PropertyMetadata(null));
public double? ScaleHeightResult
{
get { return (double?)GetValue(ScaleHeightResultProperty); }
set { SetValue(ScaleHeightResultProperty,value); }
}
// Using a DependencyProperty as the backing store for ScaleHeightResult. This enables animation,etc...
public static readonly DependencyProperty ScaleHeightResultProperty =
DependencyProperty.Register(nameof(ScaleHeightResult),new PropertyMetadata(null));
protected override Freezable CreateInstanceCore() => new ScaleCalcBinding();
}
XAML
<Window
x:Name="window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:CF2002"
x:Class="CF2002.MainWindow"
Title="MainWindow"
mc:Ignorable="d"
WindowStartupLocation="CenterScreen"
Foreground="White"
Background="#FF79C2FF"
Height="300" Width="300"
FontSize="14">
<Window.Resources>
<local:ViewModelScale x:Key="viewModel"/>
<local:ScaleCalcBinding
x:Key="ScaleCalc"
ScaleHeightResult="{Binding ScaleHeight,Mode=OneWayToSource}"
ScaleWidthResult="{Binding ScaleWidth,Mode=OneWayToSource}"
ScanScale="{Binding Text,ElementName=textBox}"
SourceElement="{Binding ElementName=grid,Mode=OneWay}"
TargetElement="{Binding ElementName=border,Mode=OneWay}"
/>
</Window.Resources>
<Window.DataContext>
<Binding Mode="OneWay" Source="{StaticResource viewModel}"/>
</Window.DataContext>
<Grid x:Name="grid">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock HorizontalAlignment="Left" />
<TextBlock HorizontalAlignment="Right" />
<TextBox x:Name="textBox" TextAlignment="Center"
Background="Transparent"
Text="5"/>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border x:Name="border" Background="LightGreen">
<StackPanel>
<TextBlock >
<Run Text="{Binding ActualWidth,ElementName=grid,Mode=OneWay}"/>
<Run Text=","/>
<Run Text="{Binding ActualHeight,Mode=OneWay}"/>
</TextBlock>
<TextBlock >
<Run Text="{Binding ActualWidth,ElementName=border,Mode=OneWay}"/>
</TextBlock>
<TextBlock >
<Run Text="{Binding ScaleWidth}"/>
<Run Text=","/>
<Run Text="{Binding ScaleHeight}"/>
</TextBlock>
</StackPanel>
</Border>
<GridSplitter Grid.Column="1" ShowsPreview="False" Width="3" Grid.RowSpan="3"
HorizontalAlignment="Center" VerticalAlignment="Stretch" />
<GridSplitter Grid.Row="1" ShowsPreview="False" Height="3" Grid.ColumnSpan="3"
VerticalAlignment="Center" HorizontalAlignment="Stretch" Tag="{Binding Mode=OneWay,Source={StaticResource ScaleCalc}}"/>
</Grid>
</Grid>
</Window>
ViewModel
public class ViewModelScale
{
private double _scaleWidth;
private double _scaleHeight;
// In property setters,recalculate coordinate values from the source collection to the collection for display.
public double ScaleWidth { get => _scaleWidth; set { _scaleWidth = value; RenderScale(); } }
public double ScaleHeight { get => _scaleHeight; set { _scaleHeight = value; RenderScale(); } }
public ObservableCollection<CustomType> ViewCollection { get; } = new ObservableCollection<CustomType>();
public ObservableCollection<CustomType> SourceCollection { get; } = new ObservableCollection<CustomType>();
private void RenderScale()
{
for (int i = 0; i < ViewCollection.Count; i++)
{
ViewCollection[i].X = SourceCollection[i].X * ScaleWidth;
ViewCollection[i].Y = SourceCollection[i].Y * ScaleHeight;
}
}
}
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。