创建不同宽度单元格的滚动网格

本文关键字:滚动 网格 单元格 创建 | 更新日期: 2023-09-27 18:20:11

我是c#和WinPhone7开发的新手,如果有人能这么友善,我需要一些指导,所以请原谅我的无知。

我遇到的问题是,我需要为电视指南创建一个网格视图,其中左侧有一个上下滚动的频道徽标垂直列表,右侧有一个水平和垂直滚动的网格。水平滚动不会移动固定在屏幕上的频道徽标,但垂直滚动网格也会滚动徽标,正如您所期望的那样。

我应该继续尝试使用XAML和Silverlight来完成这项工作,还是只通过XNA来完成?

我问这个问题是因为我已经通过Silverlight尝试了几种不同的方法来解决我遇到的两个主要问题:

性能

当我使用ASync请求从我们的API接收数据时,我创建了一个后台工作线程,该线程解析JSON,并使用Dispatch.Invoke在网格"画布"视图上创建程序单元。由于这种情况,没有逐渐的反馈,整件事都在等待,直到一切完成,然后突然出现网格。我希望单元格在不阻塞UI的情况下逐个频道或逐个单元格显示,这样滚动仍然可以很好地工作,但这似乎不会发生。

我遇到了一些问题,因为使用UIElements的任何工作都是在UI线程上完成的,而WinPhone7是主线程(我相信),这包括解析XAML或创建/修改UIElements,即使它们没有添加到屏幕上或可见的任何内容中。这意味着我无法通过预先创建或重新使用元素来改进事物。

我试着在工作线程中做尽可能多的工作,只将UI工作的一小部分发送到UI线程,以最大限度地减少阻塞,但这似乎没有帮助。

内存

很明显,我无法在7天内为每一个可能有数百个(我们支持650多个频道)的电视节目事件创建"节目单元",因为手机很快就会耗尽内存;所以我想创建一个虚拟网格,在其中创建单元格并将其加载到画布视图中,仅用于当前看到的视口。

我有两个问题:

  1. 执行任何UI工作的UI阻止如上所述停止任何滚动,因此如果不阻止UI,就无法在后台创建要滚动到视图中的新单元格
  2. 滚动视图没有发送滚动事件,我已经尝试过绑定到滚动视图中的滚动条来获得偏移值,但这并不能很好地工作,因为它只是时断时续地更新,所以如果你进行了大量滚动,我想在出现暂停或OnIdle之前,什么都不会发送

是我搞砸了,我应该坚持下去吗?还是我正在做一些不能做的事情,我应该尝试一种不同的策略,比如通过XNA来做?

如有任何建议,我们将不胜感激。

编辑:更多信息和一些示例代码

我有一个包含开始时间和标题的节目类,一个有名称和标志等的频道类,还有一系列节目。

当我检索API数据时,我会创建一个通道对象并将其添加到通道数组中,然后将程序添加到通道程序数组中。一旦一个频道的所有节目都添加到数组中,我就会将其发布到ChannelProgrammesComplete事件监听器中,以便更新UI。

<ScrollViewer ScrollViewer.VerticalScrollBarVisibility="Visible" ScrollViewer.HorizontalScrollBarVisibility="Visible">
    <Canvas x:Name="ProgGrid" 
                    Height="55" Width="393" 
                    VerticalAlignment="Top" HorizontalAlignment="Left">
    </Canvas>
</ScrollViewer>

public void ChannelProgrammesComplete( object sender, EventArgs e )
{
  var bw = new BackgroundWorker();
  bw.WorkerReportsProgress = true;
  bw.DoWork += ( doWorkSender, args ) =>
  {
    Dispatch( (Channel)sender );
  };
  bw.RunWorkerAsync();
}
private void Dispatch( BackgroundWorker bw, object param )
{
  Channel channel = (Channel)param;
  int progCount = 0;
  foreach( Programme programme in channel.Programmes )
  {
    double left = ( ( programme.StartSecsFromToday / 60 ) * PixelsPerMinute );  // turn it into seconds
    if( progCount == 0 && left < 0 )
    {
      // If first prog starts before 6am, shrink the cell so it starts at the 6am start point
      programme.UIWidth = ( ( programme.Duration - ( ( programme.StartSecsFromToday / 60 ) * -1 ) ) * PixelsPerMinute ) - _cellPadding;
      left = 0;
    }
    else
    {
      programme.UIWidth = ( programme.Duration * PixelsPerMinute ) - _cellPadding;  // Multiply by zoom level which is 3 for now, and take off the amount we use for right margin grid separator
    }
    Debug.Assert( programme.UIWidth > 0 );
    programme.UITop = channel.SortIndex * ( _rowHeight + _cellPadding );
    programme.UILeft = left;
    programme.UIHeight = _rowHeight;
    object[] invokeArgs = new object[ 1 ];
    invokeArgs[ 0 ] = programme;
    // Do as much work as possible in the thread before dispatching to the UI thread for the simple UI work
    Dispatcher.BeginInvoke( new InvokeProgrammeCellDelegate( AddProgrammeCellDelegate ), invokeArgs );
  }
}

public delegate void InvokeProgrammeCellDelegate( Programme prog );
public void AddProgrammeCellDelegate( Programme prog )
{
  Rectangle progCell = new Rectangle();
  progCell.Fill = new SolidColorBrush( Color.FromArgb( 0xFF, (byte)( 0x13 ), (byte)( 0x45 ), (byte)( 0x70 ) ) );
  progCell.Height = prog.UIHeight;
  progCell.Width = prog.UIWidth;
  progCell.SetValue( Canvas.TopProperty, prog.UITop );
  progCell.SetValue( Canvas.LeftProperty, prog.UILeft );
  ProgGrid.Children.Add( progCell );
  ProgGrid.Width = Math.Max( ProgGrid.Width, prog.UIWidth + prog.UILeft );
}

创建不同宽度单元格的滚动网格

首先,使用大的Canvas应该只是最后的手段——在大多数情况下,GridStackPanel的组合(有时在其他StackPanel中)和带有Margin的项目会更快(但处理起来会稍微困难一些)。

为了保持应用程序的响应,你可以

  1. 一次只加载小块数据(例如,两个页面。这将大大减少同时呈现的数据量)。然后,您可以立即加载下一个区块,也可以在用户滚动到列表末尾时加载
  2. 加快结果处理速度。如果您在插入每个项目后等待100ms(或者在每个频道后1秒,如果您想一次加载频道),则对响应性的影响应该几乎消失,同时对加载时间没有太大的影响。上述时间仅为估计值,如果它们太大/太小,请根据您的需求进行调整,以提供流畅的用户体验