【问题标题】:How do I know when a ListBox has finished rendering in Silverlight?我如何知道 ListBox 何时在 Silverlight 中完成渲染?
【发布时间】:2012-07-02 10:32:00
【问题描述】:

我需要知道 ListBox 第一次完成渲染的时间,以便我可以将其滚动到顶部以向用户展示列表中的第一项。

我有一个ListBox,它在DataTemplate 中使用RichTextBox

<DataTemplate x:Key="HelpTextTemplate">
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        ...
        <ContentControl>
            ...
            <RichTextBox x:Name="HelpTextContent" Grid.Row="1"
                         Tag="{Binding Path=HelpObject.Text, Mode=TwoWay}"
                         TextWrapping="Wrap"
                         HorizontalAlignment="Stretch"
                         Margin="0,0,20,0"
                         Loaded="RichTextBox_Loaded"
                         ContentChanged="RichTextBox_ContentChanged"
                         SelectionChanged="RichTextBox_SelectionChanged"/>
            ...
        </ContentControl>
        ...
    </Grid>
</DataTemplate>

ListBox 绑定到 ObservableCollection

ListBox 的滚动出现问题 - 如果RichTextBox 的高度大于ListBox 的高度,则用户无法滚动到RichTextBox 的底部。 ListBox 将跳转到列表中的下一项。滚动条滑块的高度也会改变。这是因为RichTextBox 的实际高度仅在实际渲染时计算。当它离开屏幕时,高度会恢复为较小的值(我认为代码假定文本可以全部放在一行而不是必须被换行)。

我将这些问题追溯到ListBox 使用VirtualisingStackPanel 来绘制项目。当我用简单的StackPanel 替换它时,这些问题就消失了。

这造成了我现在遇到的问题,即ListBox 在初始加载时滚动到列表底部。 ListBox 上的 LoadedLayoutUpdated 事件在数据加载之前发生。当 ObservableCollection 初始化时,我尝试在视图模型上监听 PropertyChanged 事件:

void editViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
    switch (e.PropertyName)
    {
        case "ListDataSource":
            // Try to scroll to the top of the ListBox
            break;
    }
}

这也触发得太早了。该列表此事件被触发并导致ListBox 滚动到底部后呈现。

【问题讨论】:

    标签: silverlight listbox rendering


    【解决方案1】:

    尝试在 Loaded 处理程序上滚动,但通过 Dispatcher 将其延迟一点。类似的东西

    void OnLoaded(...)
    {
       Dispatcher.BeginInvoke(() => {/*Scroll your ListBox here*/});
    }
    

    这会有所帮助。

    【讨论】:

    • 嗯。我对这样的事情持谨慎态度 - 特别是因为列表中可能有很多项目 - 但我会试一试。
    • 由于渲染发生在 UI 线程中,并且 Dispatcher.BeginInvoke 将滚动操作推送到 UI 线程的队列 - 实际上您的列表有多大并不重要。滚动应该在其他当前 UI 线程指令完成后执行。当然,如果您的集合(或控件/模板的其他部分)是异步创建的 - 它不会工作。但这是另一种情况。通常对于简单的场景它有效。
    • 我刚试过这个,恐怕没有乐趣。 Loaded 事件只调用一次。
    • 当然,如果您为 ItemsSource 分配延迟/异步,则不能依赖 Loaded 事件。我不知道细节,但如果我正确理解你的情况,只有一种方法可以实现你的目标。当集合准备好并分配时,您应该通知 UI。例如,从 ViewModel 引发一个事件并将您的视图订阅到它,然后尝试我上面提到的技巧。无论如何,由于“渲染”的方式,没有这样的事件可以通知您渲染已完成 - 它不是静态的,它是活动的。
    • 其实 LayoutUpdated 首次提出时可以使用。通常这意味着控制完成了所有的措施和安排并且准备好了(“渲染”)。但是,如果在第一次测量/排列行程后更改了控件 UI 的某些部分(或者例如控件的父容器更新等) - 布局将再次更新。因此,您应该谨慎使用这种方法,同时考虑到您的实际设计。如果你能给我提供一些简单的例子来重现你的情况,我可能会建议你更合适的。
    【解决方案2】:

    最后我不得不使用一个kludge:

    private System.Threading.Timer timer;
    void editViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
    {
        switch (e.PropertyName)
        {
            case "ListDataSource":
                TimerCallback callback = TimerResult;
                timer = new Timer(callback, null, 750, 0);
                break;
        }
    }
    
    private void TimerResult(Object stateInfo)
    {
        Dispatcher.BeginInvoke(() =>
        {
            if (this.ItemsList.Items.Count > 0)
            {
                this.ItemsList.UpdateLayout();
                this.ItemsList.SelectedIndex = 0;
                this.ItemsList.ScrollIntoView(this.ItemsList.Items[0]);
            }
        });
    
        timer.Dispose();
    }
    

    如果有人知道更好的方法,请立即发布您的答案。

    【讨论】:

      【解决方案3】:
      public class AutoScrollBehavior : Behavior<ListBox>
          {
              #region Properties
      
              public object ItemToScroll
              {
                  get { return GetValue(ItemToScrollProperty); }
                  set { SetValue(ItemToScrollProperty, value); }
              }
      
              public static readonly DependencyProperty ItemToScrollProperty = DependencyProperty.Register("ItemToScroll", typeof(object), typeof(AutoScrollBehavior), new PropertyMetadata(ItemToScrollPropertyChanged));
      
              private static void ItemToScrollPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
              {
                  ListBox lb = ((AutoScrollBehavior)d).AssociatedObject;
                  lb.UpdateLayout();
                  ((AutoScrollBehavior)d).AssociatedObject.ScrollIntoView(e.NewValue);
              }
              #endregion
          }
      

      然后在 XAML 中用作

      <ListBox SelectedItem="{Binding SelectedItemFromModel, Mode=TwoWay}">
      <i:Interaction.Behaviors>
                                                                      <Behaviors:AutoScrollBehavior ItemToScroll="{Binding SelectedItemFromModel}"/>
                                                                  </i:Interaction.Behaviors>
      </ListBox>
      

      在某些命令上,您应该能够控制和设置视图模型的 SelectedItemFromModel 属性。

      【讨论】:

        猜你喜欢
        • 2021-05-14
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-03-24
        • 2017-04-24
        相关资源
        最近更新 更多