具有动态行数和列数的网格,第2部分

源代码

下载source - 31.7 KB	下载app - 219.6 KB	GitHub

介绍

,

这篇文章专门介绍了Wpf datagrid,其中的单元格已经定义了固定的大小,但是行数和列数是动态更新的,以填充所有可用空间。例如,这种网格可以用于无限2D场的游戏或实现元胞自动机。在前一篇文章中,Wpf数据网格被认为具有动态定义的行数和列数,但所有单元格具有相同的大小。

原文发表在博客上。

特性

应用程序演示了以下功能:

所有单元格具有固定的宽度和高度;单元格的大小可以在运行时改变;行数和列数由用户控件大小定义;网格占据尽可能多的空间;单击可切换单元格的状态;异步添加/删除单元格的方法;调整计时器,防止过于频繁的细胞更新;保护细胞状态;依赖容器的使用;日志记录。

背景

解决方案使用c# 6, . net 4.6.1, Wpf与MVVM模式,NuGet包Unity和Ikc5.TypeLibrary。

解决方案

Wpf应用程序

Wpf应用程序是在一个主窗口的MVVM模式下完成的。动态网格是作为用户控件实现的,它包含绑定到单元格视图模型的可观察集合的DataGrid控件。正如上面提到的,这篇文章的代码是基于前一篇文章的代码,所以这里我们关注的是新的或者修改过的代码。

动态数据网格的视图模型包括单元格、视图和网格大小、单元格集合的数据模型和单元格视图模型集合的集合。视图大小属性绑定到数据网格控件的实际大小。实际上,从MVVM模式的角度来看,这并不是一种明确的方法,视图模型应该对视图一无所知,但它是通过绑定和附加属性来精确实现的。网格大小,即行数和列数,是按视图大小除以单元格大小计算的。由于行数和列数都是整数,视图中单元格的实际大小不能等于单元格的宽度和高度。

更改控制大小并计算网格的行数和列数后,重新创建单元格集,但保留单元格的状态。然后用异步方法更新单元格视图模型的集合。方法分析必要的更改并删除或添加行,并将单元格视图模型删除或添加到行中。异步方法允许应用程序负责,并且使用取消令牌允许在控件大小再次更改时取消更新。

动态网格控件

动态网格视图模型实现了IDynamicGridViewModel界面,该界面具有大小属性、单元格集合数据模型、单元格视图模型集合的可观察集合,以及多个颜色属性:

隐藏,收缩,复制Codepublic interface IDynamicGridViewModel
{
///
/// Width of current view - expected to be bound to view’s actual
/// width in OneWay binding.
///
int ViewWidth { get; set; }

///
/// Height of current view - expected to be bound to view’s actual
/// height in OneWay binding.
///
int ViewHeight { get; set; }

///
/// Width of the cell.
///
int CellWidth { get; set; }

///
/// Height of the cell.
///
int CellHeight { get; set; }

///
/// Count of grid columns.
///
int GridWidth { get; }

///
/// Count of grid rows.
///
int GridHeight { get; }

///
/// Data model.
///
CellSet CellSet { get; }

///
/// 2-dimensional collections for CellViewModels.
///
ObservableCollection<ObservableCollection>
Cells { get; }

///
/// Start, the lightest, color of cells.
/// s
Color StartColor { get; set; }

///
/// Finish, the darkest, color of cells.
///
Color FinishColor { get; set; }

///
/// Color of borders around cells.
///
Color BorderColor { get; set; }
}

视图宽度和高度通过附加属性绑定到数据网格控件的实际大小(代码取自这个Stackoverflow的问题):

隐藏,复制Codeattached:SizeObserver.Observe=“True”
attached:SizeObserver.ObservedWidth="{Binding ViewWidth, Mode=OneWayToSource}"
attached:SizeObserver.ObservedHeight="{Binding ViewHeight, Mode=OneWayToSource}"

调整计时器

对于视图大小的绑定有一个问题——因为绑定是在单个线程中执行的,视图宽度和高度的新值会在不同的时刻出现。这意味着有必要等待下一个。此外,为了防止过于频繁的改变网格大小,如果用户是缓慢地调整窗口,定时器在应用程序中使用。计时器是在构造函数中创建的,每当一个视图高度或视图宽度发生变化时,就会启动或重新启动计时器。

隐藏,收缩,复制Codepublic DynamicGridViewModel(ILogger logger)
{
_resizeTimer = new DispatcherTimer
{
Interval = TimeSpan.FromMilliseconds(100),
};
_resizeTimer.Tick += ResizeTimerTick;
// initialization
// …
}

protected override void OnPropertyChanged(string propertyName = null)
{
base.OnPropertyChanged(propertyName);

if (string.Equals(propertyName, nameof(ViewHeight), StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(propertyName, nameof(ViewWidth), StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(propertyName, nameof(CellHeight), StringComparison.InvariantCultureIgnoreCase) ||
string.Equals(propertyName, nameof(CellWidth), StringComparison.InvariantCultureIgnoreCase))
{
ImplementNewSize();
}
}

///
/// Start timer when one of the view’s dimensions is changed and wait for another.
///
private void ImplementNewSize()
{
if (ViewHeight == 0 || ViewWidth == 0)
return;

if (_resizeTimer.IsEnabled)
_resizeTimer.Stop();

_resizeTimer.Start();
}

当计时器计时时,方法检查宽度和高度是否有效并重新创建单元格。然后方法CreateOrUpdateCellViewModels更新单元格视图模型集合的可观察集合被执行:

隐藏,收缩,复制Code///
/// Method change data model and grid size due to change of view size.
///
///
///
private void ResizeTimerTick(object sender, EventArgs e)
{
_resizeTimer.Stop();

if (ViewHeight == 0 || ViewWidth == 0)
return;

var newWidth = System.Math.Max(1, (int)System.Math.Ceiling((double)ViewWidth / CellWidth));
var newHeight = System.Math.Max(1, (int)System.Math.Ceiling((double)ViewHeight / CellHeight));
if (CellSet != null &&
GridWidth == newWidth &&
GridHeight == newHeight)
{
// the same size, nothing to do
return;
}

// preserve current points
var currentPoints = CellSet?.GetPoints().Where(point => point.X < newWidth && point.Y < newHeight);
CellSet = new CellSet(newWidth, newHeight);
GridWidth = CellSet.Width;
GridHeight = CellSet.Height;

if (currentPoints != null)
CellSet.SetPoints(currentPoints);
CreateOrUpdateCellViewModels();
}

更新单元格视图模型的集合

在创建了新的单元格集之后,应该更新单元格视图模型的集合。在上一篇文章中,每次都重新创建这个集合,这会导致应用程序挂起。通过更新当前集合的异步方法解决了这个问题。由于Wpf架构和动态网格用户控制项源绑定到单元格集合,该集合的所有更改都是通过Dispatcher完成的。在应用程序中的优先级DispatcherPriority。ApplicationIdle在所有数据绑定之后执行时使用,但是可以使用其他值。

起始点是方法CreateOrUpdateCellViewModels,该方法在第一次创建单元格集合,创建取消令牌,并为第一行启动异步循环方法CreateCellViewModelsAsync。

隐藏,收缩,复制Codeprivate async void CreateOrUpdateCellViewModels()
{
_logger.LogStart(“Start”);

// stop previous tasks that creates viewModels
if (_cancellationSource != null && _cancellationSource.Token.CanBeCanceled)
_cancellationSource.Cancel();

if (Cells == null)
Cells = new ObservableCollection<ObservableCollection>();

try
{
_cancellationSource = new CancellationTokenSource();
await CreateCellViewModelsAsync(0, _cancellationSource.Token).ConfigureAwait(false);
}
catch (OperationCanceledException ex)
{
_logger.Exception(ex);
}
catch (AggregateException ex)
{
foreach (var innerException in ex.InnerExceptions)
{
_logger.Exception(innerException);
}
}
finally
{
_cancellationSource = null;
}
_logger.LogEnd(“Completed - but add cells in asynchronous way”);
}

由于单元格视图模型存储为集合的集合,每个内部集合对应于网格的行。方法CreateCellViewModelsAsync对从0到Math.Max(单元格)的每一行位置执行。数,GridHeight)。可能出现下列情况:

rowNumber >= GridHeight,这意味着集合单元格包含的行比当前网格大小的行多。这些行应该是re移动:

隐藏,复制CodeApplication.Current.Dispatcher.Invoke (
() =比;Cells.RemoveAt (positionToProcess),
DispatcherPriority.ApplicationIdle,
cancellationToken);
rowNumber & lt;细胞。计数,这意味着具有该索引的行存在于集合单元格中,且索引小于网格高度。在这种情况下,方法UpdateCellViewModelRow被调用:
隐藏,复制CodeApplication.Current.Dispatcher.Invoke (
() =比;UpdateCellViewModelRow (positionToProcess),
DispatcherPriority.ApplicationIdle,
cancellationToken);
我们注意,这一行是observablection /icellviewmodel>。根据此集合的长度和网格宽度之间的关系,删除额外的单元格视图模型,使用动态网格数据模型中的新的ICell实例更新现有的单元格模型,并添加丢失的单元格视图模型:

隐藏,复制代码/ / / & lt; summary>
///在行中添加或删除单元格视图模型。
/ / / & lt; / summary>
/// 数据模型的行数。
私有void UpdateCellViewModelRow(int rowNumber)
{
var row =单元格[rowNumber];
//删除额外的单元格
而行。数比;GridWidth)
row.RemoveAt (GridWidth);
for (var pos = 0;pos & lt;GridWidth;pos + +)
{
//创建新的视图模型或更新现有的视图模型
var cell = CellSet。rowNumber GetCell (pos);
如果(pos & lt;row.Count)
行(pos)。细胞=细胞;
其他的
{
var cellViewModel = new cellViewModel (cell);
row.Add (cellViewModel);
}
}
}
“else”情况,即rowNumber >=单元格。Count和rowNumber <表示收集单元格不包含必要的行。这个行是通过方法CreateCellViewModelRow创建的:
隐藏,复制代码/ / / & lt; summary>
///添加新的对应于的单元格视图模型行
///数据模型中的行数。
/ / / & lt; / summary>
/// 数据模型的行数。
私有void CreateCellViewModelRow(int rowNumber)
{
_logger。Log($“Create {rowNumber} row of cells”);
var row = new observablection();
for (var x = 0;x & lt;GridWidth;x + +)
{
var cellViewModel = new cellViewModel (CellSet)。GetCell (x, rowNumber));
row.Add (cellViewModel);
}

_logger。Log($"{rowNumber} row of cells is ready for rendering");
Cells.Add(行);
}

依赖容器

Unity被用作依赖容器。在这篇文章中,我们将EmptyLogger注册为logger,并为DynamicGridViewModel的实例创建singleton。在Wpf应用程序中DI容器的初始化是在App.xaml.cs的OnStartup方法中完成的:

隐藏,复制Codeprotected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);

IUnityContainer container = new UnityContainer();
container.RegisterType<ILogger, EmptyLogger>();

var dynamicGridViewModel = new DynamicGridViewModel(
container.Resolve())
{
// init properties
};

container.RegisterInstance(
typeof(IDynamicGridViewModel),
dynamicGridViewModel,
new ContainerControlledLifetimeManager());

var mainWindow = container.Resolve();
Application.Current.MainWindow = mainWindow;
Application.Current.MainWindow.Show();
}

MainWindow构造函数的参数是由容器解析的:

隐藏,复制Codepublic MainWindow(IDynamicGridViewModel dynamicGridViewModel)
{
InitializeComponent();
DataContext = dynamicGridViewModel;
}

同理,DynamicGridViewModel构造函数的输入参数由容器来解析:

隐藏,复制Codepublic class DynamicGridViewModel : BaseNotifyPropertyChanged, IDynamicGridViewModel
{
private readonly ILogger _logger;

public DynamicGridViewModel(ILogger logger)
{
logger.ThrowIfNull(nameof(logger));
_logger = logger;

this.SetDefaultValues();
// initialization
// ...
_logger.Log("DynamicGridViewModel constructor is completed");

}
// other methods
// …
}

历史

2017.01.04最初的帖子。

本文转载于:http://www.diyabc.com/frontweb/news270.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值