优化由 WPF 中的单元格组成的网格以获得最短路径 [英] Optimizing a Grid consisting of cells in WPF for Shortest Path

查看:14
本文介绍了优化由 WPF 中的单元格组成的网格以获得最短路径的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我目前正在尝试制作一个由 WPF 中的 Cell 对象组成的网格.我需要将单元格绑定到需要在二维数组中的对象.- 我需要它很大,可扩展,并改变单元格的颜色并将数据存储在对象中!

I'm currently trying to make a grid consisting of Cell objects in WPF. I need to bind the cells to objects, which needs to be in a 2D array. - And i need it to be big, scalable and to change color of cells and store data in the objects!

我已经实现了,但是绘制网格似乎很慢!(100x100 网格需要 >10 秒!)这是我已经制作的图片:

I have an implementation made, but it seems to be very slow to draw the grid! (100x100 grid takes >10 secs!) Here is a picture of what i have already made:

我在 ItemsControl 的 XAML 中使用数据绑定.这是我的 XAML:

I'm using databinding in XAML in an ItemsControl. Here is my XAML:

<ItemsControl x:Name="GridArea" ItemsSource="{Binding Cellz}" Grid.Column="1" BorderBrush="Black" BorderThickness="0.1">
        <ItemsControl.Resources>
            <DataTemplate DataType="{x:Type local:Cell}">
                <Border BorderBrush="Black" BorderThickness="0.1">
                    <Grid Background="{Binding CellColor}">
                        <i:Interaction.Triggers>
                            <i:EventTrigger EventName="MouseMove" >
                                <ei:CallMethodAction TargetObject="{Binding}" MethodName="MouseHoveredOver"/>
                            </i:EventTrigger>
                        </i:Interaction.Triggers>
                    </Grid>
                </Border>

            </DataTemplate>
        </ItemsControl.Resources>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Rows="{Binding Rows}" Columns="{Binding Columns}" MouseDown="WrapPanelMouseDown" MouseUp="WrapPanelMouseUp" MouseLeave="WrapPanelMouseLeave" >
                    <!--<UniformGrid.Background>
                        <ImageBrush/>
                    </UniformGrid.Background>-->
                </UniformGrid>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>

在我的代码隐藏中,我实例化了一个名为 Grid 的类的对象,它使用 Cell 类的对象制作了一个二维数组(和一个列表,这是我绑定到的).用秒表检查后,我可以看到这不花时间.它是实际的绑定和绘制网格,所以我想我的优化应该发生在我的 XAML 中,如果有任何优化可用.

In my codebehind i instantiate a object of a class called Grid, which makes a 2D array (and a list, which is what im binding to) with objects of the Cell class. After some checking with a stopwatch, i can see that this is not taking the time. Its the actual binding and drawing the grids, so i guess my optimization should happen in my XAML, if any optimization is available.

但是为了提供一切,这里是我背后的代码以及网格类和单元类:

But to provide everything, here is my code behind and the grid class and the cell class:

    public MainWindow()
    {
        InitializeComponent();  

        NewGrid = new Grid(75, 75);
        DataContext = NewGrid;

    }

    public class Grid
    {
    public int Columns { get; set; }
    public int Rows { get; set; }

    public ObservableCollection<Cell> Cellz {get;set;}

    public Cell[,] CellArray { get; set; }

    public Grid(int columns, int rows)
    {
        Columns = columns;
        Rows = rows;

        Cellz = new ObservableCollection<Cell>();
        CellArray = new Cell[Rows,Columns];
        InitializeGrid();

    }

    public void InitializeGrid()
    {
        Color col = Colors.Transparent;
        SolidColorBrush Trans = new SolidColorBrush(col);
        for (int i = 0; i < Rows; i++)
        {
            for (int j = 0; j < Columns; j++)
            {
                var brandNewCell = new Cell(i, j) { CellColor = Trans};
                Cellz.Add(brandNewCell);
                CellArray[i, j] = brandNewCell;
            }
        }
    }

    public class Cell : INotifyPropertyChanged
    {
        public int x, y;   // x,y location
        public Boolean IsWall { get; set; }

        private SolidColorBrush _cellcolor;
        public SolidColorBrush CellColor
        {
            get { return _cellcolor; }
            set
            {
                _cellcolor = value;
                OnPropertyChanged();
            }
        }
        public Cell(int tempX, int tempY)
        {
            x = tempX;
            y = tempY;
        }


        public bool IsWalkable(Object unused)
        {
            return !IsWall;
        }
    public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(
            [CallerMemberName] string caller = "")
        {
            if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(caller));
        }
    }
}

我喜欢带有绑定的非常简单的实现,但是加载时间真的不可接受 - 任何建议将不胜感激!

I like the quite simple implementation with binding, however the loadtime is really unacceptable - Any advice would be greatly appreciated!

推荐答案

好吧,我重新创建了您的示例,但做了一些更改.我主要摆脱了 DataContext 上的绑定,并专门为您的用例创建了一个视图模型,它直接绑定到项目控件.

Well, i recreated your example, with some changes. I mainly got rid of the bindings on the DataContext, and created a viewmodel specifically for your use case, that gets bound directly to the itemscontrol.

绘制速度肯定在10秒以内,但我想我给了你尽可能多的相关代码,以便你可以比较解决方案...

The speed of drawing is definitely under the 10 seconds, but I thought i gave you as much of the relevant code as possible so you could compare the solutions...

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using TestSO.model;

namespace TestSO.viewmodel
{
    public class ScreenViewModel : INotifyPropertyChanged, IDisposable
    {
        public event PropertyChangedEventHandler PropertyChanged;

        private IList<Cell> cells;
        public IList<Cell> Cells
        {
            get
            {
                return cells;
            }
            set
            {
                if (object.Equals(cells, value))
                {
                    return;
                }
                UnregisterSource(cells);
                cells = value;
                RegisterSource(cells);
                RaisePropertyChanged("Cells");
            }
        }

        private int rows;
        public int Rows
        {
            get
            {
                return rows;
            }
            set
            {
                if (rows == value)
                {
                    return;
                }
                rows = value;
                RaisePropertyChanged("Rows");
            }
        }

        private int columns;
        public int Columns
        {
            get
            {
                return columns;
            }
            set
            {
                if (columns == value)
                {
                    return;
                }
                columns = value;
                RaisePropertyChanged("Columns");
            }
        }

        private Cell[,] array;
        public Cell[,] Array
        {
            get
            {
                return array;
            }
            protected set
            {
                array = value;
            }
        }

        protected void RaisePropertyChanged(string propertyName)
        {
            var local = PropertyChanged;
            if (local != null)
            {
                App.Current.Dispatcher.BeginInvoke(local, this, new PropertyChangedEventArgs(propertyName));
            }
        }

        protected void RegisterSource(IList<Cell> collection)
        {
            if (collection == null)
            {
                return;
            }
            var colc = collection as INotifyCollectionChanged;
            if (colc != null)
            {
                colc.CollectionChanged += OnCellCollectionChanged;
            }
            OnCellCollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace, collection, null));
        }

        protected virtual void OnCellCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.Action == NotifyCollectionChangedAction.Reset)
            {
                Array = null;
            }
            if (e.OldItems != null)
            {
                foreach (var item in e.OldItems)
                {
                    var cell = item as Cell;
                    if (cell == null)
                    {
                        continue;
                    }
                    if (Array == null)
                    {
                        continue;
                    }
                    Array[cell.X, cell.Y] = null;
                }
            }
            if (e.NewItems != null)
            {
                if (Array == null)
                {
                    Array = new Cell[Rows, Columns];
                }
                foreach (var item in e.NewItems)
                {
                    var cell = item as Cell;
                    if (cell == null)
                    {
                        continue;
                    }
                    if (Array == null)
                    {
                        continue;
                    }
                    Array[cell.X, cell.Y] = cell;
                }
            }
        }

        protected void UnregisterSource(IList<Cell> collection)
        {
            if (collection == null)
            {
                return;
            }
            var colc = collection as INotifyCollectionChanged;
            if (colc != null)
            {
                colc.CollectionChanged -= OnCellCollectionChanged;
            }
            OnCellCollectionChanged(collection, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }

        public ScreenViewModel()
        {
        }

        public ScreenViewModel(int row, int col)
            : this()
        {
            this.Rows = row;
            this.Columns = col;
        }

        bool isDisposed = false;
        private void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (isDisposed)
                {
                    return;
                }
                isDisposed = true;
                Cells = null;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
    }
}

我创建了一个额外的控制器,它是 ObservableCollection 的所有者,主要目的是不对 viewModel 做任何更改,而是更改控制器内的集合(或添加添加、删除、清除方法),并让事件链为我完成工作,使 2Dimensional Array 在 ScreenViewModel 中保持最新

And i created as an extra, a controller, who is the owner of the ObservableCollection, main purpose would be to not do any changes on the viewModel, but rather change the collection inside the controller (or add add, remove, clear methods to it), and let the event chain do the work for me, keeping the 2Dimensional Array up to date in the ScreenViewModel

using System.Collections.Generic;
using System.Collections.ObjectModel;
using TestSO.model;

namespace TestSO.controller
{
    public class GenericController<T>
    {
        private readonly IList<T> collection = new ObservableCollection<T>();
        public IList<T> Collection
        {
            get
            {
                return collection;
            }
        }

        public GenericController()
        {
        }
    }

    public class CellGridController : GenericController<Cell>
    {
        public CellGridController()
        {
        }
    }
}

和你的单元格类,我稍微调整了一下,只在实际发生变化的情况下引发更改事件

And your cell class, i slightly adjusted it with only raising the change event in case there was actually a change

using System.ComponentModel;
using System.Windows;
using System.Windows.Media;

namespace TestSO.model
{
    public class Cell : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void RaisePropertyChanged(string propertyName)
        {
            var local = PropertyChanged;
            if (local != null)
            {
                Application.Current.Dispatcher.BeginInvoke(local, this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private int x;
        public int X
        {
            get
            {
                return x;
            }
            set
            {
                if (x == value)
                {
                    return;
                }
                x = value;
                RaisePropertyChanged("X");
            }
        }

        private int y;
        public int Y
        {
            get
            {
                return y;
            }
            set
            {
                if (y == value)
                {
                    return;
                }
                y = value;
                RaisePropertyChanged("Y");
            }
        }

        private bool isWall;
        public bool IsWall
        {
            get
            {
                return isWall;
            }
            set
            {
                if (isWall == value)
                {
                    return;
                }
                isWall = value;
                RaisePropertyChanged("IsWall");
            }
        }

        private SolidColorBrush _cellColor;
        public SolidColorBrush CellColor
        {
            get
            {
                // either return the _cellColor, or say that it is transparent
                return _cellColor ?? Brushes.Transparent;
            }
            set
            {
                if (SolidColorBrush.Equals(_cellColor, value))
                {
                    return;
                }
                _cellColor = value;
                RaisePropertyChanged("CellColor");
            }
        }

        public Cell()
        {
        }

        public Cell(int x, int y)
            : this()
        {
            this.X = x;
            this.Y = y;
        }
    }
}

然后我稍微修改了一下xaml(虽然没有接管交互点),通过为ScreenViewModel创建资源,控制器,还有一个DataTemplate,这个Template,然后也是DataTemplate直接添加到ItemsControl之上ItemTemplate,而不是使用 DataTemplate 功能(没有将其视为上述要求吗?)

Then I changed the xaml a bit (though didn't take over the interaction points), by creating the resources for the ScreenViewModel, the controller, and a DataTemplate, this Template, is then also DataTemplate directly added to the ItemsControl over the ItemTemplate, instead of using the DataTemplate functionality (didn't see that as a requirement above?)

<Window x:Class="TestSO.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:model="clr-namespace:TestSO.model"
        xmlns:viewmodel="clr-namespace:TestSO.viewmodel"
        xmlns:controller="clr-namespace:TestSO.controller"
        Title="MainWindow" Height="350" Width="525">
    <Window.Resources>
        <controller:CellGridController x:Key="CellController" />
        <viewmodel:ScreenViewModel x:Key="GridViewModel" Rows="75" Columns="75" />
        <DataTemplate x:Key="CellTemplate">
            <Border BorderBrush="Black" BorderThickness="0.5">
                <Grid Background="{Binding CellColor}">
                </Grid>
            </Border>
        </DataTemplate>
    </Window.Resources>
    <Grid>
        <ItemsControl ItemsSource="{Binding Cells,Source={StaticResource GridViewModel}}" BorderBrush="Black" BorderThickness="0.1" ItemTemplate="{StaticResource CellTemplate}">
            <ItemsControl.ItemsPanel>
                <ItemsPanelTemplate>
                    <UniformGrid
                        Rows="{Binding Rows,Source={StaticResource GridViewModel}}" 
                        Columns="{Binding Columns,Source={StaticResource GridViewModel}}" />
                </ItemsPanelTemplate>
            </ItemsControl.ItemsPanel>
        </ItemsControl>
    </Grid>
</Window>

在 main.cs 页面中,我加载了控制器的 Collection 与 ScreenViewModel.Cells 属性,并加载了一些模板数据.只是非常基本的模拟数据(您也可以将 screenmodel 附加到 DataContext 并在其他地方定义控制器,并更改 xaml 中的绑定以返回到 DataContext,但是通过资源,您还可以访问已创建的实例(在 initializeComponent 之后)

And inside main.cs page, i loaded I linked the Collection of the controller with the ScreenViewModel.Cells property, and loaded some template data. Just pretty basic mock data (you could also attach the screenmodel to the DataContext and define the controller somewhere else, and change the bindings in the xaml to go back to the DataContext, however over the resources, you can also get to the already created instances (after initializeComponent)

protected ScreenViewModel ScreenViewModel
{
    get
    {
        return this.Resources["GridViewModel"] as ScreenViewModel;
    }
}

protected CellGridController Controller
{
    get
    {
        return this.Resources["CellController"] as CellGridController;
    }
}

protected void Load()
{
    var controller = Controller;
    controller.Collection.Clear();
    string[] rows = colorToCellSource.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
    string row;
    for (int x = 0; x < rows.Length; x++)
    {
        int length = rows[x].Length;
        ScreenViewModel.Rows = rows.Length;
        ScreenViewModel.Columns = length;
        row = rows[x];
        for (int y = 0; y < length; y++)
        {
            Cell cell = new Cell(x, y);
            cell.CellColor = row[y] == '0' ? Brushes.Transparent : Brushes.Blue;
            controller.Collection.Add(cell);
        }
    }
}

public MainWindow()
{
    InitializeComponent();
    if (Controller != null && ScreenViewModel != null)
    {
        ScreenViewModel.Cells = Controller.Collection;
        Load();
    }
}

屏幕需要在 1 秒内重新绘制,调整大小和最大化需要一点延迟,但我想这是可以预期的......(我的测试模板是 105x107)

The screen takes gets redrawn here under 1 second, resizing and maximizing takes a small delay, but i guess that can be expected... (My test template was 105x107)

这篇关于优化由 WPF 中的单元格组成的网格以获得最短路径的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆