【问题标题】:How to draw hundred points efficiently, C#/XAML如何有效地绘制百点,C#/XAML
【发布时间】:2016-03-24 02:51:38
【问题描述】:

我要模拟一个 30*90 的点阵显示。 on 的点 (LED) 为红色,off 的点 (LED) 为黑色。

我玩过代码,创建了 2700 Ellipse,也使用了this technique,在任何情况下程序使用了太多的 RAM(大约 80mb)并且存在性能问题(更新所有点时存在延迟)。

我的问题是:我怎样才能有效地解决这个问题?

【问题讨论】:

  • 快速提问,点是圆形(形状、多边形)还是点(像素)?
  • 现在我正在使用 System.Windows.Shapes.Ellypse 但不是严格规则
  • 好吧,你总是可以去集成一个不错的 direct3d 表面以获得最佳性能。
  • “更新所有点”是什么意思?是重新创建/添加了 2700 个点的列表,还是只是更新所有点的颜色?

标签: c# wpf xaml


【解决方案1】:

下面的确切代码需要 C#6,但它使用的少数 C#6 东西可以用更低版本的更烦人的代码来完成。在 Windows 7 x64 上使用 .NET 4.6.1 进行测试。

在大多数情况下,这对于 WPF 开发人员来说应该是相当标准的东西。绑定模式设置为我们使用的,以挤出更多。我们使用PropertyChangedEventArgs 的单个实例,而不是您通常看到的“newing one up”,因为这会给 GC 增加更多不必要的压力,并且您说 80 MB 是“很多”,所以这已经在推动它了。

对复杂内容的内联评论,主要关注 XAML。

唯一有点“有趣”的是ItemsControl / Canvas 技巧。我想我是从over here 那里得到的。

这里的驱动程序的早期编辑每次更新都会一次翻转所有点,但这并没有真正展示您在保留绑定时可以做的事情。它还让转换器将 Color 值公开为 SolidColorBrush 对象的包装器(忘记了我可以将这些公开为 Brush 属性,而 TypeConverter 将使其在 XAML 中设置同样容易)。

XAML

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:me="clr-namespace:WpfApplication1"
        Title="MainWindow"
        DataContext="{Binding RelativeSource={RelativeSource Self}, Mode=OneTime}"
        SizeToContent="WidthAndHeight">
    <Window.Resources>
        <!-- A value converter that we can use to convert a bool property
             to one of these brushes we specify, depending on its value. -->
        <me:BooleanToBrushConverter x:Key="converter"
                                    TrueBrush="Red"
                                    FalseBrush="Black" />
    </Window.Resources>

    <!-- The main "screen".  Its items are the Dots, which we only need to read
         once (the collection itself doesn't change), so we set Mode to OneTime. -->
    <ItemsControl Width="300"
                  Height="900"
                  ItemsSource="{Binding Dots, Mode=OneTime}">

        <!-- We use just a single Canvas to draw dots on. -->
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <Canvas />
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>

        <!-- On each presenter, set the Canvas.Left and Canvas.Top attached
             properties to the X / Y values.  Again, the dots themselves don't
             move around after being initialized, so Mode can be OneTime. -->
        <ItemsControl.ItemContainerStyle>
            <Style TargetType="ContentPresenter">
                <Setter Property="Canvas.Left"
                        Value="{Binding Path=XPos, Mode=OneTime}" />

                <Setter Property="Canvas.Top"
                        Value="{Binding Path=YPos, Mode=OneTime}" />
            </Style>
        </ItemsControl.ItemContainerStyle>

        <!-- Now, we just need to tell the ItemsControl how to draw each Dot.
             Width and Height are 10 (this is the same 10 that we multiplied x and
             y by when the Dots were created).  The outline is always black.
             As for Fill, we use IsOn to tell us which Brush to use.  Since IsOn
             is a bool, we use our converter to have it toggle the Brush. -->
        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type me:Dot}">
                <Ellipse Width="10"
                         Height="10"
                         Stroke="Black"
                         Fill="{Binding IsOn,
                                        Mode=OneWay,
                                        Converter={StaticResource converter}}" />
            </DataTemplate>
        </ItemsControl.Resources>
    </ItemsControl>
</Window>

代码隐藏

using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            // Initialize all the dots.
            var dotSeq = from x in Enumerable.Range(0, 30)
                         from y in Enumerable.Range(0, 90)
                         select new Dot(x * 10, y * 10);

            Dot[] allDots = dotSeq.ToArray();
            this.Dots = new ReadOnlyCollection<Dot>(allDots);

            // Start a dedicated background thread that picks a random dot,
            // flips its state, and then waits a little while before repeating.
            BackgroundWorker bw = new BackgroundWorker();
            bw.DoWork += delegate { RandomlyToggleAllDots(allDots); };
            bw.RunWorkerAsync();

            this.InitializeComponent();
        }

        public ReadOnlyCollection<Dot> Dots { get; }

        private static void RandomlyToggleAllDots(Dot[] allDots)
        {
            Random random = new Random();
            while (true)
            {
                Dot dot = allDots[random.Next(allDots.Length)];
                dot.IsOn = !dot.IsOn;
                Thread.Sleep(1);
            }
        }
    }
}

点.cs

using System.ComponentModel;

namespace WpfApplication1
{
    public sealed class Dot : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        internal Dot(double xPos, double yPos)
        {
            this.XPos = xPos;
            this.YPos = yPos;
        }

        public double XPos { get; }

        public double YPos { get; }

        #region IsOn

        // use a single event args instance
        private static readonly PropertyChangedEventArgs IsOnArgs =
            new PropertyChangedEventArgs(nameof(IsOn));

        private bool isOn;
        public bool IsOn
        {
            get
            {
                return this.isOn;
            }

            set
            {
                if (this.isOn == value)
                {
                    return;
                }

                this.isOn = value;
                this.PropertyChanged?.Invoke(this, IsOnArgs);
            }
        }

        #endregion IsOn
    }
}

BooleanToBrushConverter.cs

using System;
using System.Globalization;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;

namespace WpfApplication1
{
    [ValueConversion(typeof(bool), typeof(Brush))]
    public sealed class BooleanToBrushConverter : IValueConverter
    {
        public Brush TrueBrush { get; set; }
        public Brush FalseBrush { get; set; }

        public object Convert(object value, Type _, object __, CultureInfo ___) =>
            (bool)value ? this.TrueBrush : this.FalseBrush;

        public object ConvertBack(object _, Type __, object ___, CultureInfo ____) =>
            DependencyProperty.UnsetValue; // unused
    }
}

【讨论】:

  • 另一种方法可能是在一个源集合上使用 2 个ListCollectionViews 来执行此操作,该源集合在 IsOn 属性上使用实时整形,或者可能不是实时整形,我们只是在整个持续时间内推迟刷新的完整更新。 w.r.t的相对优势和劣势。性能部分取决于每次更新期间预计会改变多少点。
  • 您的解决方案效果很好,我正在努力理解所有内容。如果您添加一些 cmets,我将不胜感激。谢谢!。
  • @alecardv:我简化了一些事情,添加了更多 cmets,并将驱动程序更改为仅在循环中翻转随机选择的点,而不是每次迭代时随机重置每个点。
【解决方案2】:

您可能需要考虑使用 DrawingVisual 类,这是在 WPF 中绘制矢量图像的最高性能方式 - 请参阅 MSDN link。这种方法很像在 Winforms 中使用 GDI+。请注意,命中测试/自动布局/绑定等内容在此级别不可用 - 这对您可能很重要,也可能不重要。

【讨论】:

  • 肯定会消耗更少的内存,但我不确定是否值得失去绑定的能力。反正我会试试的。谢谢。
猜你喜欢
  • 1970-01-01
  • 2016-04-26
  • 1970-01-01
  • 1970-01-01
  • 2017-01-11
  • 1970-01-01
  • 2011-06-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多