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


    源代码

    介绍 , 这篇文章专门介绍了Wpf datagrid,其中的单元格已经定义了固定的大小,但是行数和列数是动态更新的,以填充所有可用空间。例如,这种网格可以用于无限2D场的游戏或实现元胞自动机。在前一篇文章中,Wpf数据网格被认为具有动态定义的行数和列数,但所有单元格具有相同的大小。 原文发表在博客上。 特性 应用程序演示了以下功能: 所有单元格具有固定的宽度和高度;单元格的大小可以在运行时改变;行数和列数由用户控件大小定义;网格占据尽可能多的空间;单击可切换单元格的状态;异步添加/删除单元格的方法;调整计时器,防止过于频繁的细胞更新;保护细胞状态;依赖容器的使用;日志记录。 背景 解决方案使用c# 6, . net 4.6.1, Wpf与MVVM模式,NuGet包Unity和Ikc5.TypeLibrary。 解决方案 Wpf应用程序 Wpf应用程序是在一个主窗口的MVVM模式下完成的。动态网格是作为用户控件实现的,它包含绑定到单元格视图模型的可观察集合的DataGrid控件。正如上面提到的,这篇文章的代码是基于前一篇文章的代码,所以这里我们关注的是新的或者修改过的代码。 动态数据网格的视图模型包括单元格、视图和网格大小、单元格集合的数据模型和单元格视图模型集合的集合。视图大小属性绑定到数据网格控件的实际大小。实际上,从MVVM模式的角度来看,这并不是一种明确的方法,视图模型应该对视图一无所知,但它是通过绑定和附加属性来精确实现的。网格大小,即行数和列数,是按视图大小除以单元格大小计算的。由于行数和列数都是整数,视图中单元格的实际大小不能等于单元格的宽度和高度。 更改控制大小并计算网格的行数和列数后,重新创建单元格集,但保留单元格的状态。然后用异步方法更新单元格视图模型的集合。方法分析必要的更改并删除或添加行,并将单元格视图模型删除或添加到行中。异步方法允许应用程序负责,并且使用取消令牌允许在控件大小再次更改时取消更新。 动态网格控件 动态网格视图模型实现了IDynamicGridViewModel界面,该界面具有大小属性、单元格集合数据模型、单元格视图模型集合的可观察集合,以及多个颜色属性: 隐藏,收缩,复制Code

    public interface IDynamicGridViewModel
    {
      /// <summary>
      /// Width of current view - expected to be bound to view's actual
      /// width in OneWay binding.
      /// </summary>
      int ViewWidth { get; set; }
    
      /// <summary>
      /// Height of current view - expected to be bound to view's actual
      /// height in OneWay binding.
      /// </summary>
      int ViewHeight { get; set; }
    
      /// <summary>
      /// Width of the cell.
      /// </summary>
      int CellWidth { get; set; }
    
      /// <summary>
      /// Height of the cell.
      /// </summary>
      int CellHeight { get; set; }
    
      /// <summary>
      /// Count of grid columns.
      /// </summary>
      int GridWidth { get; }
    
      /// <summary>
      /// Count of grid rows.
      /// </summary>
      int GridHeight { get; }
    
      /// <summary>
      /// Data model.
      /// </summary>
      CellSet CellSet { get; }
    
      /// <summary>
      /// 2-dimensional collections for CellViewModels.
      /// </summary>
      ObservableCollection<ObservableCollection<ICellViewModel>>
        Cells { get; }
    
      /// <summary>
      /// Start, the lightest, color of cells.
      /// </summary>s
      Color StartColor { get; set; }
    
      /// <summary>
      /// Finish, the darkest, color of cells.
      /// </summary>
      Color FinishColor { get; set; }
    
      /// <summary>
      /// Color of borders around cells.
      /// </summary>
      Color BorderColor { get; set; }
    }

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

    attached:SizeObserver.Observe="True"
    attached:SizeObserver.ObservedWidth="{Binding ViewWidth, Mode=OneWayToSource}"
    attached:SizeObserver.ObservedHeight="{Binding ViewHeight, Mode=OneWayToSource}"

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

    public 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();
      }
    }
    
    /// <summary>
    /// Start timer when one of the view's dimensions is changed and wait for another.
    /// </summary>
    private void ImplementNewSize()
    {
      if (ViewHeight == 0 || ViewWidth == 0)
        return;
    
      if (_resizeTimer.IsEnabled)
        _resizeTimer.Stop();
    
      _resizeTimer.Start();
    }

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

    /// <summary>
    /// Method change data model and grid size due to change of view size.
    /// </summary>
    /// <paramname="sender"></param>
    /// <paramname="e"></param>
    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。 隐藏,收缩,复制Code

    private 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<ICellViewModel>>();
    
      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> /icellviewmodel>。根据此集合的长度和网格宽度之间的关系,删除额外的单元格视图模型,使用动态网格数据模型中的新的ICell实例更新现有的单元格模型,并添加丢失的单元格视图模型: 隐藏,复制代码/ / / & lt; summary> ///在行中添加或删除单元格视图模型。 / / / & lt; / summary> /// <param name="rowNumber">数据模型的行数。</param> 私有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> /// <param name="rowNumber">数据模型的行数。</param> 私有void CreateCellViewModelRow(int rowNumber) { _logger。Log($"Create {rowNumber} row of cells"); var row = new observablection<ICellViewModel>(); 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方法中完成的: 隐藏,复制Code

    protected override void OnStartup(StartupEventArgs e)
    {
      base.OnStartup(e);
    
      IUnityContainer container = new UnityContainer();
      container.RegisterType<ILogger, EmptyLogger>();
    
      var dynamicGridViewModel = new DynamicGridViewModel(
                              container.Resolve<ILogger>())
      {
        // init properties
      };
    
      container.RegisterInstance(
        typeof(IDynamicGridViewModel),
        dynamicGridViewModel,
        new ContainerControlledLifetimeManager());
    
      var mainWindow = container.Resolve<MainWindow>();
      Application.Current.MainWindow = mainWindow;
      Application.Current.MainWindow.Show();
    }

    MainWindow构造函数的参数是由容器解析的: 隐藏,复制Code

    public MainWindow(IDynamicGridViewModel dynamicGridViewModel)
    {
      InitializeComponent();
      DataContext = dynamicGridViewModel;
    }

    同理,DynamicGridViewModel构造函数的输入参数由容器来解析: 隐藏,复制Code

    public 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/news271.html

  • 相关阅读:
    Miracast
    linux软中断与硬中断实现原理概述
    入门视频采集与处理(BT656简介)
    emms指令在MMX指令中的作用
    linux进程的地址空间,核心栈,用户栈,内核线程
    linux 线程的内核栈是独立的还是共享父进程的?
    进程内核栈、用户栈及 Linux 进程栈和线程栈的区别
    Gson JsonParser的使用
    封装JDBC事务操作,执行存储过程测试
    Oracle 存储过程,临时表,动态SQL测试
  • 原文地址:https://www.cnblogs.com/Dincat/p/13437342.html
Copyright © 2020-2023  润新知