问题
I have created a sample app where I have a grouped ListView and while scrolling through the listview I want to get the top visible group in the UI (first visible group). But when I keep scrolling on a touch based device, it starts showing the next group (next to the top visible group) for as long as I keep my finger on the device. If I remove my fingers, it would correct the value. Here is my sample app: https://onedrive.live.com/redir?resid=91B2B9D9EA21A110!615&authkey=!AKJV0b_q7g-YZF4&ithint=file%2czip
Code from sample:
public sealed partial class MainPage : Page
{
public ViewModel MyVM = new ViewModel();
public MainPage()
{
this.InitializeComponent();
lv.SizeChanged += (s, e) =>
{
ScrollViewer sv = FindVisualChildren<ScrollViewer>(lv).FirstOrDefault();
if (sv != null)
{
sv.ViewChanged += (ss, ee) =>
{
IEnumerable<TextBlock> tblocks = FindVisualChildren<TextBlock>(lv).Where(x => x.Name == "tbHeader");
if (tblocks != null)
{
foreach (TextBlock tblock in tblocks)
{
if (IsVisibileToUser(tblock, sv))
{
first.Text = tblock.Text;
break;
}
}
}
};
}
};
}
private static IEnumerable<T> FindVisualChildren<T>(DependencyObject depObj) where T : DependencyObject
{
if (depObj != null)
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++)
{
DependencyObject child = VisualTreeHelper.GetChild(depObj, i);
if (child != null && child is T)
{
yield return (T)child;
}
foreach (T childOfChild in FindVisualChildren<T>(child))
{
yield return childOfChild;
}
}
}
}
private bool IsVisibileToUser(FrameworkElement element, FrameworkElement container)
{
if (element == null || container == null)
return false;
if (element.Visibility != Visibility.Visible)
return false;
Rect elementBounds = element.TransformToVisual(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect containerBounds = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return (elementBounds.Top < containerBounds.Bottom && elementBounds.Bottom > containerBounds.Top);
}
}
public class ClassA
{
public DateTime DateTimePropertyOfClassA { get; set; }
}
public class ViewModel
{
public ViewModel()
{
//return a grouped collection:
Grouped = from x in CollectionOfClassA group x by x.DateTimePropertyOfClassA into grp orderby grp.Key select grp;
}
public IList<ClassA> CollectionOfClassA { get; set; } = new List<ClassA>()
{
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-01-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-04-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-05-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-06-01")},
new ClassA(){ DateTimePropertyOfClassA =DateTime.Parse("2016-07-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-08-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-09-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-11-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-12-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA =DateTime.Parse("2016-06-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-01-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA = DateTime.Parse("2016-03-01")},
new ClassA(){ DateTimePropertyOfClassA =DateTime.Parse("2016-06-01")}
};
public IEnumerable<object> Grouped { get; }
}
Xaml:
<Page.Resources>
<CollectionViewSource x:Name="cvs"
IsSourceGrouped="True"
Source="{x:Bind MyVM.Grouped, Mode=OneWay}"/>
</Page.Resources>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Name="first" HorizontalAlignment="Center" Visibility="Visible" FontSize="12"></TextBlock>
<ListView Grid.Row="1" Name="lv" ItemsSource="{Binding Source={StaticResource cvs}}" ScrollViewer.VerticalScrollBarVisibility="Visible">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock x:Name="tbHeader" FontSize="15" FontWeight="Bold" Text="{Binding Key}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</Grid>
Is there anything I can do to improve this functionality?
回答1:
ScrollViewer.ViewChanged event will be fired several times during scrolling, we can't control how many times the IsVisibileToUser method be called in your code.
Rect elementBounds will be dynamically changed during scrolling, here is a workaround you can try: reducing the region in the ScrollViewer to only one TextBlock's size, for example:
private bool IsVisibileToUser(FrameworkElement element, FrameworkElement container)
{
if (element == null || container == null)
return false;
if (element.Visibility != Visibility.Visible)
return false;
Rect elementBounds = element.TransformToVisual(container).TransformBounds(new Rect(0.0, 0.0, element.ActualWidth, element.ActualHeight));
Rect containerBounds = new Rect(0.0, 0.0, container.ActualWidth, container.ActualHeight);
return (elementBounds.Top <= element.ActualHeight && elementBounds.Bottom > containerBounds.Top);
}
来源:https://stackoverflow.com/questions/37853167/getting-the-first-visible-group-in-listview-does-not-work-properly