当前位置: 首页 > 编程日记 > 正文

UWP: ListView 中与滚动有关的两个需求的实现

在 App 的开发过程中,ListView 控件是比较常用的控件之一。掌握它的用法,能帮助我们在一定程度上提高开发效率。本文将会介绍 ListView 的一种用法——获取并设置 ListView 的滚动位置,以及获取滚动位置处的项目。这里多说一句,由于这个描述有点,所以本文的标题实在不好起。

举个例子,如果你正在开发的应用有这样一个需求,当用户从一个列表页(包括 ListView 控件)返回到前一页面时,你需要得到用户在浏览 ListView 中的内容到哪个位置以及哪一项了,以便告诉用户最近浏览项,并且可以让用户再次打开列表时,直接从上次浏览的位置处继续浏览。如下图:

本文介绍了实现上述需求的方法。具体来说,这个需求可细分为两个小需求,即:

  1. 获取、设置 ListView 的滚动位置;
  2. 获取 ListView 滚动位置处的项目。

以下我会通过上面配图中的 Demo 应用逐一说明(本文末尾有源码下载链接),这个 Demo 包括两个页面,一个主页 (MainPage),一个列表页 (ItemsPage)。主页中包括:

  • 按钮:可以导航到 ItemsPage;
  • 最近浏览信息区域:可以查看上次浏览的项目,并提供一个按钮可以导航到列表页中上次浏览的项目处;

而列表页,则包括一个 ListView 控件,展示若干个项目。

一、获取、设置 ListView 的滚动位置

关于获取、设置 ListView 的滚动位置,微软已经提供了相关的例子,我在这个 Demo 中是直接套用的。这个功能主要是通过 ListViewPersistenceHelper 来实现的,它提供以下两个方法:

       // 获取 ListView 的滚动位置public static string GetRelativeScrollPosition(ListViewBase listViewBase, ListViewItemToKeyHandler itemToKeyHandler)
// 设置 ListView 的滚动位置public static IAsyncAction SetRelativeScrollPositionAsync(ListViewBase listViewBase, String relativeScrollPosition, ListViewKeyToItemHandler keyToItemHandler)

这两个方法中各有一个参考是委托类型,分别是 ListViewItemToKeyHandler 和 ListViewKeyToItemHandler,它们的作用是告诉这个类如何处理列表项与 Key 的对应关系,好使得该类可以正确地获取或设置滚动位置。这里的 Key 是 ListViewItem 所代表的项目的一个属性(比如 Demo 中 Item 类的 Id 属性),这个属性的值在整个列表中是唯一的;而 Item 是在 Item 对象本身。在 Demo 中它们的实现分别如下:

        private string ItemToKeyHandler(object item){Item dataItem = item as Item;if (dataItem == null) return null;return dataItem.Id.ToString();}private IAsyncOperation<object> KeyToItemHandler(string key){Func<System.Threading.CancellationToken, Task<object>> taskProvider = token =>{var items = listView.ItemsSource as List<Item>;if (items != null){var targetItem = items.FirstOrDefault(m => m.Id == int.Parse(key));return Task.FromResult((object)targetItem);}else{return Task.FromResult((object)null);}};return AsyncInfo.Run(taskProvider);}

实现这两个方法后,重载列表页的  OnNavigatingFrom 方法,在其中加入以下代码,来实现获取滚动位置并保存:

           string position = ListViewPersistenceHelper.GetRelativeScrollPosition(this.listView, ItemToKeyHandler);NavigationInfoHelper.SetInfo(targetItem, position);

继续为页面注册 Loaded 事件,在 Loaded 事件中加入以下代码来实现设置滚动位置:

            if (navigationParameter != null){if (NavigationInfoHelper.IsHasInfo){await ListViewPersistenceHelper.SetRelativeScrollPositionAsync(listView, NavigationInfoHelper.LastPosition, KeyToItemHandler);}}

这里需要注意的是,设置滚动位置的方法是异步的,所以 Loaded 方法需要加上 async 修饰符。而上述代码中对 navigationParameter 参数的判断则是为了区别:在导航时是否定位到最近浏览的位置,具体可参考 Demo 的代码。

二、获取 ListView 滚动位置处的项目

关于第二个需求的实现,我们首先需要明白以下三点:

  1. ListView 的模板 (Template) 中包括 ScrollViewer,我们可以通过 VisualTreeHelper 获取到此控件;
  2. ListView 提供 ContainerFromItem 方法,它使们可以通过传递 Item 获取包括此 Item 的 Container,即 ListViewItem;
  3. UIElement 提供 TransformToVisual 方法,可以得到某控件相对指定控件的位置转换信息;

所以我们的思路就是:得到 ListView 控件中的 ScrollViewer,并遍历 ListView 中所有的 Item,在遍历过程中,得到每一项目的 ListViewItem,并判断它的位置是否位于 ScrollViewer 的位置中。以下是获取 ListView 中当前所有可见项的代码:

        public static List<T> GetAllVisibleItems<T>(this ListViewBase listView){var scrollViewer = listView.GetScrollViewer();if (scrollViewer == null){return null;}List<T> targetItems = new List<T>();foreach (T item in listView.Items){var itemContainer = listView.ContainerFromItem(item) as FrameworkElement;bool isVisible = IsVisibileToUser(itemContainer, scrollViewer, true);if (isVisible){targetItems.Add(item);}}return targetItems;}

在上述代码的 foreach 循环中的部分,正是我们前述思路的体现。而其中所调用的 IsVisibleToUser 方法,则是如何判断某一 ListViewItem 是否在 ScrollViewer 中为当前可见。其代码如下:

        /// <summary>/// Code from here:///  https://social.msdn.microsoft.com/Forums/en-US/86ccf7a1-5481-4a59-9db2-34ebc760058a/uwphow-to-get-the-first-visible-group-key-in-the-grouped-listview?forum=wpdevelop/// </summary>/// <param name="element">ListViewItem or element in ListViewItem</param>/// <param name="container">ScrollViewer</param>/// <param name="isTotallyVisible">If the element is partially visible, then include it. The default value is false</param>/// <returns>Get the visibility of the target element</returns>private static bool IsVisibileToUser(FrameworkElement element, FrameworkElement container, bool isTotallyVisible = false){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);if (!isTotallyVisible){return (elementBounds.Top < containerBounds.Bottom && elementBounds.Bottom > containerBounds.Top);}else{return (elementBounds.Bottom < containerBounds.Bottom && elementBounds.Top > containerBounds.Top);}}

可以看出,我们是能过得到两个 Rect 值。Rect 类型的值代表一个矩形区域的位置和大小,我们对这两个值进行比较后,返回最终的结果。
获取 ListViewItem 的 Rect 值: element.TransformToVisual(container) 返回的结果是 GeneralTransform 类型,这个值表明了 ListViewItem 相对于 Container(即 ScrollViewer)的位置转换信息。GeneralTransform 类型可能我们并不太熟悉,不过,从它派生出来的这些类: ScaleTransform、TranslateTransform ,我们就熟悉了,GeneralTransform 正是它们的基类。GeneralTransform 包括以下两个重要的方法:

  1. TransformPoint, 可以将得到的转换信息计算成 Point 值,表示某控件相对于另一控件的坐标位置
  2. TransformBounds,可以将得到的转换信息计算成 Rect 值,表示某控件相对于另一控件的坐标位置及所占的区域。

所以,我们通过 TransformBounds 方法就得到了 ListViewItem 相对于 ScrollViewer 的位置和所占区域的信息。
获取 ScrollViewer 的 Rect 值: 直接实例化一个 Rect,以 0,0 作为你左上角的坐标位置点, ScrollViewer 的 ActualWidth 和 ActualHeight 作为其大小。

接下来,就是比较的过程:这里,我们做了一个判断,判断是否要求元素 (ListViewItem) 完全在 ScrollViewer 中(而非仅部分在其中)。如果要求部分显示即可,则只要元素的 Top 小于 Container 的 Bottom 值,并且元素的 Bottom 大于 Container 的 Top;如果要求全部显示,那么算法是:元素的 Top 大于 Container 的 Top 并且元素的 Bottom 小于 Container 的 Bottom。如果您对语言描述或者代码都还不明白,也可以在纸上画一下进行比较。

接下来,我们照着 GetAllVisbleItems 方法的思路可以实现 GetFirstVisibleItem 方法,即获取列表中第一个可见项,代码可参考 Demo 的源码,在此不再赘述。

我们在之前重载的方法 OnNavigatingFrom 中加上这句代码,即可以获取到用户浏览位置处的那一项。

          var targetItem = this.listView.GetFirstVisibleItem<Item>();

至此,所有主要功能已经基本完成。

结语

本文介绍了如何获取和设置 ListView 的滚动位置,以及获取滚动位置处的那一项,前者主要是借助于 ListViewPersistenceHelper 来实现,后者则是通过获取 ListViewItem 和 ScrollViewer 的 Rect 值并进行比较而最终实现的。如果您有更好的方法、不同的看见,请留言,共同交流。

源码下载

参考资料:

ListView Sample
How to get the first visible group key in the grouped listview

转载于:https://www.cnblogs.com/wpinfo/p/6437487.html

相关文章:

Deepin ROMS 安装详细流程

按照这个过程&#xff0c;完美安装&#xff0c;当然并不能排除会出现其他的问题。如果遇到了&#xff0c;那就老老实实上网搜吧。 转载于:https://www.cnblogs.com/haoorhuai/p/9502859.html

java struts2值栈ognl_Struts2 (三) — OGNL与值栈

一、OGNL表达式1.概述1.1什么是OGNL​ OGNL是Object-Graph Navigation Language的缩写&#xff0c;俗称对象图导航语言. 它是一种功能强大的表达式语言&#xff0c;通过它简单一致的表达式语法&#xff0c;可以存取对象的任意属性&#xff0c;调用对象的方法&#xff0c;遍历整…

c语言课程设计商品销售系统,c语言课程设计商品销售管理系统.pdf

C语言课程设计商品销售管理系统12020 年 4 月 19 日文档仅供参考商品销售管理系统目录一、 需求分析 2二、概要设计 2三、详细设计 4四、调试分析 14五、用户手册 14六、测试数据 15七、 附录 18— 1—22020 年 4 月 19 日文档仅供参考一、需求分析商品销售管理程序商品信息&am…

uploadhandler.php,WordPress Kernel Theme ‘upload-handler.php’任意文件上传漏洞

javascript中apply、call和bind的区别在JS中,这三者都是用来改变函数的this对象的指向的,他们有什么样的区别呢.在说区别之前还是先总结一下三者的相似之处:1.都是用来改变函数的this对象的指向的.2.第一个参数都是this要指向的对 ...AIX UNIX 系统管理、维护与高可用集群建设—…

selenium--driver.switchTo()

在自动化测试中&#xff0c;会遇到多窗口、多iframe、多alert的情况。此时&#xff0c;会使用driver.switchTo()来解决。 下面时关于driver.switchTo()的详细介绍&#xff1a; 1.多windows操作。 在页面A上操作时&#xff0c;点击某个元素之后&#xff0c;可能会打开新的窗口。…

代理模式-积木模式

代理模式&#xff0d;积木模式 代理模式在实际开发中的确非常常见和常用&#xff0c;表面上代理模式是产生出一个代理类&#xff0c;作为访问实际实现类的代理&#xff0c;控制了外界对实际代理类的访问&#xff0c;在此基础上增加一些增强性的功能。实际上&#xff0c;还将实际…

java调用系统时间函数_JAVA自学笔记:不使用系统函数来计算日期处于当年的第多少天...

前段时间练习了一个求闰年的功能&#xff0c;现在就可以用上啦&#xff0c;这次写一个不借助类库的时间函数&#xff0c;利用基础代码写一个计算求日期处于当年的第多少天的函数。虽然也有简单的方法&#xff0c;例如直接定义每月的天数累加&#xff0c;然后加上当月的天数就可…

c语言输入学生成绩q退出,哭诉、拜求C语言学生成绩管理系统

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼/* Note:Your choice is C IDE */#include "stdio.h"#include "stdlib.h"#include "conio.h"#include "string.h"struct student{int num;char name[20];float score1;float score2;float…

php 所有魔术方法,PHP常用的魔术方法及规则

转&#xff1a;https://www.cnblogs.com/wanglijun/p/10926303.html1. __construct 具有构造函数的类会在每次创建新对象时先调用此方法;初始化工作执行。2. __desstruct 对象的所有引用都被删除或者当对象被显式销毁时执行。3.__call()在对象中调用一个不可访问方法时&#xf…

mysql启动后在哪里编程_启动mysql后怎么连接数据库

推荐答案一.mysql安装百度mysql下载一个即可。只是注意一下几个重要的数据&#xff1a;第一个就是Port Number :3306。端口号默认3306&#xff0c;一般不需要改&#xff0c;如果改了&#xff0c;请记住这个端口号。第二个就是password&#xff1a;本地数据库密码&#xff0c;默…

Python 出现 can't use a string pattern on a bytes-like object

# codingutf-8import urllib.requestimport reurl http://www.163.comfile d:/test.htmldata urllib.request.urlopen(url).read() r1 re.compile(<.*?>)c_t r1.findall(data)print(c_t)发现读取下来后,运行到第9 行,出现: cant use a string pattern on a bytes-l…

2018牛客网暑期ACM多校训练营(第十场)J(二分)

题目描述&#xff1a; 给你n个字符串&#xff0c;要让它们一个一个的合并成一个串。在合并的过程中&#xff0c;要保证被合并的串S是合并后的串r的前缀&#xff0c;模式串t是串r的子序。问你将这n个字符串合并后所得到的字典序最小的串是什么。 题目分析&#xff1a; 首先&…

实用c语言函数源码,C语言编写简单朗读小工具(有源码)

原标题&#xff1a;C语言编写简单朗读小工具(有源码)最近不少人在后台留言说学C都是面对枯燥的控制台程序&#xff0c;能不能体现一下C语言的实际用途&#xff0c;今天我们就理论结合实践一把&#xff1a;C语言结合VBS脚本编写一个简单的朗读小工具&#xff0c;做一个能够发音的…

php监听订单状态,ecshop数据库订单状态判断

order_info 表刚下完订单order_status 0shipping_status 0pay_status 0取消order_status 2shipping_status 0pay_status 0确认order_status 1shipping_status 0pay_status 0已付款order_status 1shipping_status 0pay_status 2配货中order_status 1shipping_status 3pay_status…

flask异步操作_Python Flask后端异步处理(三)

前一篇博文我们已经将基础知识和环境配置进行了介绍&#xff1a;首先编写一个celerytask.py文件进行Celery的配置&#xff0c;同时耗时任务也写在该文件中from celery import Celeryfrom init import appfrom SZheConsole import SZheScanapp.config[CELERY_BROKER_URL] redis…

Codeforces Round #308 (Div. 2) C. Vanya and Scales dfs

题目链接: http://codeforces.com/contest/552/problem/C 题意: 给你100个砝码&#xff0c;第i个砝码质量是w^i&#xff0c;然后问你能不能在有m的情况下&#xff0c;左边和右边都放砝码&#xff0c;使得这个天平平衡 题解: dfs直接暴力 对于这个砝码来说&#xff0c;只有3种选…

java中JVM的原理【转】

一、java虚拟机的生命周期&#xff1a; Java虚拟机的生命周期 一个运行中的Java虚拟机有着一个清晰的任务&#xff1a;执行Java程序。程序开始执行时他才运行&#xff0c;程序结束时他就停止。你在同一台机器上运行三个程序&#xff0c;就会有三个运行中的Java虚拟机。 Java虚拟…

switch的case使用数组C语言,使用常量数组的元素作为switch语句中的case

我正在尝试将一组按键映射到一组命令.因为我处理来自多个地方的命令,所以我想在键和命令之间设置一个抽象层,这样如果我更改底层键映射,我就不必更改很多代码.我目前的尝试看起来像这样:// input.henum LOGICAL_KEYS {DO_SOMETHING_KEY,DO_SOMETHING_ELSE_KEY,...countof_LOGIC…

PHP上传文件函数move_upload,如何使用php中move_uploaded_file函数

我们平时上传的文件保存在临时文件夹中&#xff0c;例如/ tmp&#xff0c;但临时文件夹的内容在一段时间后会被删除&#xff0c;因此为了将来要使用上传文件&#xff0c;需要将内容保存在不太可能被任意删除的专用目录中&#xff0c;这时就需要使用move_uploaded_file函数&…

java的标记接口_Java中的标记接口?

我被教授&#xff0c;Java中的Marker接口是一个空接口&#xff0c;用于向编译器或JVM发送信号&#xff0c;实现此接口的类的对象必须以特殊方式处理&#xff0c;如序列化&#xff0c;克隆等。但最近我了解到&#xff0c;它实际上与编译器或JVM无关。例如&#xff0c;在Serializ…

Java Exception

先贴上一段Exception源码注释 1 /**2 * The class {code Exception} and its subclasses are a form of3 * {code Throwable} that indicates conditions that a reasonable4 * application might want to catch.5 *6 * <p>The class {code Exception} and any subc…

c语言实验至少包括四个函数中,C语言实验报告《函数》

学号&#xff1a;__________ 姓名&#xff1a;__________ 班级&#xff1a;__________ 日期&#xff1a;__________指导教师&#xff1a;__________ 成绩&#xff1a;__________实验四 函数一、 实验目的1、掌握函数定义、调用和声明的方法2、掌握实参和形参之间的…

Android与iOS对比

最近有并行开发Android与iOS端App,想在这总结一些两种开发的相似与区别。转载于:https://www.cnblogs.com/stuwan/p/6475725.html

oracle停止一切进程,oracle启动/停止的几种方法以及 启动和停止过程中出错的解决办法...

一、启动几种方法&#xff1a;1、sqlplus /nologconnect /as sysdbastartup2、sqlplus /nologconnect /as sysdbastartup nomountalter database mountalter database open在以上两种方法中&#xff0c;1方法中的startup相当于2方法中的startup nomountalter database mountalt…

前端js判断上传是否为EXCEL表格问题

直接贴代码吧~JS部分 输入框部分&#xff1a; 转载于:https://www.cnblogs.com/aijiajia1314/p/9517541.html

java 外部类似_[求指点] 如何用java 实现类似linux中管道调用外部程序的功能

想写个小程序实现类似linux中管道的功能&#xff0c;创建一个外部子进程&#xff0c;然后主进程不断地写输入给子进程&#xff0c;而后把子进程的返回值取出。如下的小代码就是从stdin读入一个字符串&#xff0c;调用子进程(cat)返回这个串&#xff0c;然后返回。但下面的写法只…

c语言递归求五阶行列式源代码,久游堂怎么样 -官网

iOS版# -*- coding: utf-8 -*- """ author: Dell Created on Tue Dec 24 12:33:56 2019 """ import time from selenium import webdriver from selenium.webdriver.support.ui import WebDriverWait#等待一个元素加载完成 from selenium.webdri…

POJ - 3660 Cow Contest(flod)

题意&#xff1a;有N头牛&#xff0c;M个关系&#xff0c;每个关系A B表示编号为A的牛比编号为B的牛强&#xff0c;问若想将N头牛按能力排名&#xff0c;有多少头牛的名次是确定的。 分析&#xff1a; 1、a[u][v]1表示牛u比牛v强&#xff0c;flod扫一遍&#xff0c;可以将所有牛…

oracle scn与数据恢复,SCN与数据库恢复的关系

一。SCN与CHECKPOINTCKPT进程在checkpoint发生时&#xff0c;将当时的SCN号写入数据文件头和控制文件&#xff0c;同时通知DBWR进程将数据块写到数据文件。CKPT进程也会在控制文件中记录RBA(redo block address),以标志Recovery需要从日志中哪个地方开始。与checkpoint相关的SC…

Java 理解泛型的基本含义

Java 泛型 Java 泛型&#xff08;generics&#xff09;是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制&#xff0c;该机制允许程序员在编译时检测到非法的类型。 泛型的本质是参数化类型&#xff0c;也就是说所操作的数据类型被指定为一个参数。 泛型方法 你可…