WPF ItemControl慢速渲染

如何解决WPF ItemControl慢速渲染

您好,我有一个WPF应用程序,其中有一个图像,用户可以在其中选择一个区域,一旦选择了该区域,就会在所选区域上方出现一个带有叉号的网格。
我来回应用一些变换,以便缩放和旋转网格以匹配图像坐标。

到目前为止,这种方法已经可以使用了,但是当有很多交叉时(〜+ 5k)UI冻结并花了一些时间来绘制十字。我已经应用了一些在Stackoverflow上找到的答案,例如虚拟化,ListViewListBox,但我无法使其正常工作。我想知道是否有人可以在这里介绍一下,谢谢!!

编辑
所以我最终要完成所有相关的计算,以便在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.LeftCanvas.TopCanvas面板上布置项目容器。

诸如缩放之类的图形操作应直接在视图模型中对数据项集进行。要允许动态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 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-