关于viewport/container的消息传递

本文探讨了在特定UI框架中Viewport与Container之间的消息传递机制,分析了不同触摸模式下的消息处理方式及其引发的问题,包括消息传递中断及子控件接收不到消息等现象。

我通过把两个widget放到container内组装了一个新的控件,在测试时发现了问题,container内的两个widget无法接收消息,于是对container小研究了一下。在brew下container内如果插入的内容太多,超过它的宽度和高度,是直接通过滚动来显示的,需要先用一个viewport来装饰这个container,此时可以直接拖拉来看区域外的内容,为了更直观些,想要看到滚动条,需要再用一个scrollbar来装饰这个viewport.在我现在的应用中,有些区域内可能东西比较多,需要拖拉来显示,但加上滚动条看起来比较怪,所以就直接用了viewport.在初始化container时传进个参数,如果为真表示支持拖拉,此时需要viewport,如果为假,则不支持拖拉,直接初始化一个container就行了。然而用viewport装饰时,viewport的flag有三个值,分别为VWF_SIZETOFIT,VWF_DISABLEKEYHANDLING,VWF_CONSUMETOUCHEVENTS 。如果为了能让viewport支持拖拉,需要设置flag为VWF_CONSUMETOUCHEVENTS(我个人认为,因为看了一些文档说需要这样设置,但没找到文档说必须设置为这个值),还需要设定rootcontainer支持触摸事件IWidget_EnableTouch(pMe->m_pRootWidget);还需要设定它的touchmode,常用的主要有两种,AEEWIDGET_TOUCH_MODE_ALWAYS和AEEWIDGET_TOUCH_MODE_CHILD,文档里有说明用法,我也被他们折磨了很久。

(1)touchmode为AEEWIDGET_TOUCH_MODE_ALWAYS时。因为设置了viewport为VWF_CONSUMETOUCHEVENTS,消息传到它这里全部被它所“消费”,也就是说消息将不再向下传下去,所以,需要自己写程序把消息传给它的孩子,主要是通过事件的位置,算出命中的widget,并调用它的handleEvent函数,即把消息传递给它,但现在遇到的一个问题是,如果viewport里面是一个container,container里面是一个button,现在点击这个button,viewport先捕捉到消息,并把这个消息吃掉,然后在它的消息处理函数里面调用container的handleEvent函数,而通常情况下,container是不需要注册handle_event函数的,系统会自动把消息传给它内部的孩子,但此处消息已经被上层的viewport吃掉,所以,消息到ontainer后,无法再向下传递给它的孩子,我的工程就是用的这种方法,之前一直没有在viewport中插入过container,所以一直也没有发现这个问题(注:viewport中嵌套viewport消息是能正常传递的),这个问题把我卡了两个星期,中间尝试了第二种方法,感觉比这一种要好,但还是有个问题没解决。有个同事告诉我说系统内部有个消息链,在viewport这里消息链断掉的,我需要自己再把消息告诉container,让它继续把消息传下去。话虽这么说,但我却无从下手,考虑过container也做handleEvent函数,通过位置去投递消息,但这个项目当时设计的时候是特意去掉container的消息处理函数的,让系统自己传递消息来提高速度,我如果这样一改的话,等于把当时的设计又推翻了,最后折腾了很久,才通过IWidget_HandleEvent把消息传递下去,不过我的时间都是再第二种方法上浪费的,我相信第二种方法一定可以,可有个问题我一直没搞定。。。

(2)touchmode为AEEWIDGET_TOUCH_MODE_CHILD时。这种方法的话viewport会自动把消息传给他的孩子,应该比较智能。也支持拖拉。brew官网上有一个例子。如果正常使用的话我觉得可以满足一切需求了。但是目前我遇到的问题是给viewport设置过extent之后,例如我修改了它的例子,开始viewport 的extent设置为50*50,然后把它插入到rootcontainer内,接着修改它的extent为100*100,而它所修饰的container的大小为200*200,这时,可以拖拉viewport查看container内部所有的内容,但是container内的widget,只有在(0,0,50,50)的范围内才能接收到消息,也就是说调整后的viewport没有生效。我试了很多方法都没用。一般情况下viewport是不需要调整高度的,但我的项目初始化的时候不知道它的高度,先创建所有的widget和建立它们的关系,然后再 统一layout,所以没办法使用,浪费了很多时间,最终还是采用上面的方法了。

帮我看看为什么pdf无法拖动 <template> <Teleport to="body"> <div v-if="visible" class="pdf-mask" @mousedown="startDrag"> <div ref="modalRef" class="pdf-modal" :style="modalStyle"> <div class="pdf-header"> <span class="title">报关资料</span> <div class="tools"> <el-button size="small" @click="zoomIn">放大</el-button> <el-button size="small" @click="zoomOut">缩小</el-button> <el-button size="small" @click="close">关闭</el-button> </div> </div> <!-- 滚动容器 --> <el-tabs v-model="activeTab" class="pdf-tabs"> <el-tab-pane v-for="tab in tabs" :key="tab.id" :label="tab.name" :name="tab.id"> <!-- 单个 PDF 容器 --> <div class="pdf-body" ref="scrollRef" @wheel.prevent="handleWheel($event, tab.id)" @mousedown="onPdfMouseDown"> <div :ref="(el) => setPdfContainer(tab.id, el)" /> </div> </el-tab-pane> </el-tabs> </div> </div> </Teleport> </template> <script setup lang="ts"> import { ref, reactive, watch, nextTick, onMounted } from 'vue' import * as pdfjsLib from 'pdfjs-dist' import pdfjsWorker from 'pdfjs-dist/build/pdf.worker?worker&url' pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker const emit = defineEmits<{ (e: 'update:visible', v: boolean): void (e: 'tabschange', id: string | number): void }>() const pdfRealSize = ref({ width: 0, height: 0 }) /* tabs 数据 */ const tabs = ref<Array<{ id: string | number, name: string, url: string }>>([]) const activeTab = ref<string | number>('') async function openWithTabs( list: Array<{ code: string; name: string; url: string }> ) { tabs.value = list .filter((i) => i.name !== 'all_files') .map((i) => ({ id: i.code, name: i.name, url: i.url })) const firstId = tabs.value[0]?.id activeTab.value = firstId ?? '' // 手动让弹窗显示 emit('update:visible', true) // 等 DOM 更新完,立即渲染第一页 await nextTick() if (firstId) { const container = pdfContainerMap.get(firstId) const tab = tabs.value.find((t) => t.id === firstId) if (container && tab) { await renderSinglePDF(tab.url, container) } } // 弹框居中 const cw = window.innerWidth, ch = window.innerHeight modalStyle.left = (cw - 800) / 2 + 'px' modalStyle.top = (ch - 600) / 2 + 'px' } const pdfContainerMap = new Map<string | number, HTMLDivElement>() function setPdfContainer(id: string | number, el: any) { if (el) pdfContainerMap.set(id, el as HTMLDivElement) } watch(activeTab, (id) => { const tab = tabs.value.find(t => t.id === id) if (!tab) return renderSinglePDF(tab.url, pdfContainerMap.get(id)!) }) /* ---------------- props & emits ---------------- */ const props = defineProps<{ visible: boolean url?: string highlight?: { x0: number; y0: number; x1: number; y1: number } | null }>() /* ---------------- 弹窗位置 ---------------- */ const modalRef = ref<HTMLDivElement>() const pdfContainer = ref<HTMLDivElement>() const scrollRef = ref<HTMLDivElement>() const modalStyle = reactive({ left: '0px', top: '0px', width: '800px', height: '600px', cursor: 'default' }) let dragStart: { x: number; y: number } | null = null /* ---------------- 拖拽 ---------------- */ function startDrag(e: MouseEvent) { const target = e.target as HTMLElement if (!target.classList.contains('pdf-mask') && !target.closest('.pdf-header')) return if (!modalRef.value) return dragStart = { x: e.clientX, y: e.clientY } modalStyle.cursor = 'grabbing' document.addEventListener('mousemove', onDrag) document.addEventListener('mouseup', stopDrag) e.preventDefault() } function onDrag(e: MouseEvent) { if (!dragStart) return const dx = e.clientX - dragStart.x const dy = e.clientY - dragStart.y modalStyle.left = parseFloat(modalStyle.left) + dx + 'px' modalStyle.top = parseFloat(modalStyle.top) + dy + 'px' dragStart = { x: e.clientX, y: e.clientY } } function stopDrag() { dragStart = null modalStyle.cursor = 'default' document.removeEventListener('mousemove', onDrag) document.removeEventListener('mouseup', stopDrag) if (modalRef.value) { modalStyle.width = modalRef.value.offsetWidth + 'px' modalStyle.height = modalRef.value.offsetHeight + 'px' } } /* ---------------- 缩放 ---------------- */ // 每个 tab 独立缩放比例 const tabScale = new Map<string | number, number>() const getScale = (id: string | number) => tabScale.get(id) ?? 1 const setScale = (id: string | number, v: number) => tabScale.set(id, Math.min(Math.max(v, 0.5), 3)) // 放大 / 缩小 const zoomIn = () => { setScale(activeTab.value, getScale(activeTab.value) + 0.2); rerenderActiveTab() } const zoomOut = () => { setScale(activeTab.value, getScale(activeTab.value) - 0.2); rerenderActiveTab() } // 重新渲染当前 tab async function rerenderActiveTab() { const id = activeTab.value const tab = tabs.value.find(t => t.id === id) const container = pdfContainerMap.get(id) if (tab && container) await renderSinglePDF(tab.url, container) } // 鼠标滚轮缩放 async function handleWheel(e: WheelEvent, tabId: string | number) { e.preventDefault() e.stopPropagation() // 获取当前tab的容器 const container = pdfContainerMap.get(tabId) if (!container) return // 获取滚动容器 const scrollContainer = container.parentElement if (!scrollContainer) return // 获取鼠标在滚动容器中的位置 const rect = scrollContainer.getBoundingClientRect() const mouseX = e.clientX - rect.left + scrollContainer.scrollLeft const mouseY = e.clientY - rect.top + scrollContainer.scrollTop // 获取当前缩放比例 const currentScale = getScale(tabId) // 计算新的缩放比例 const delta = e.deltaY > 0 ? -0.1 : 0.1 const newScale = Math.min(Math.max(currentScale + delta, 0.5), 3) // 如果缩放比例没有变化,直接返回 if (Math.abs(newScale - currentScale) < 0.01) return // 保存当前滚动位置和鼠标位置 const scrollLeft = scrollContainer.scrollLeft const scrollTop = scrollContainer.scrollTop // 更新缩放比例 setScale(tabId, newScale) // 重新渲染PDF const tab = tabs.value.find(t => t.id === tabId) if (tab) { await renderSinglePDF(tab.url, container) // 重新计算滚动位置,保持鼠标位置不变 const scaleRatio = newScale / currentScale const newScrollLeft = mouseX * scaleRatio - (e.clientX - rect.left) const newScrollTop = mouseY * scaleRatio - (e.clientY - rect.top) // 设置新的滚动位置 scrollContainer.scrollTo({ left: newScrollLeft, top: newScrollTop }) } } // renderSinglePDF 改用当前 tab 的 scale let baseViewport: pdfjsLib.PageViewport | null = null const tabBaseViewport = new Map<string | number, pdfjsLib.PageViewport>() async function renderSinglePDF(url: string, container: HTMLDivElement) { if (!container) return container.innerHTML = '' const pdf = await pdfjsLib.getDocument(url).promise const page = await pdf.getPage(1) baseViewport = page.getViewport({ scale: 1 }) // ← 保存原始 viewport tabBaseViewport.set(activeTab.value, baseViewport) const scale = getScale(activeTab.value) * (800 / baseViewport.width) const vp = page.getViewport({ scale }) const canvas = document.createElement('canvas') canvas.width = vp.width canvas.height = vp.height const ctx = canvas.getContext('2d')! container.appendChild(canvas) await page.render({ canvasContext: ctx, viewport: vp, canvas }).promise; // 新增:更新滚动容器尺寸 pdfRealSize.value = { width: vp.width, height: vp.height } const scrollBox = container.parentElement as HTMLDivElement if (scrollBox) { scrollBox.style.width = vp.width + 'px' scrollBox.style.height = vp.height + 'px' } } /* ---------------- PDF 逻辑宽高 ---------------- */ const pdfLogicWidth = ref(0) const pdfLogicHeight = ref(0) /* ---------------- PDF 渲染 ---------------- */ async function renderPDF() { if (!pdfContainer.value || !props.url) return pdfContainer.value.innerHTML = '' try { const pdf = await pdfjsLib.getDocument(props.url).promise const page = await pdf.getPage(1) const baseViewport = page.getViewport({ scale: 1 }) pdfLogicWidth.value = baseViewport.width pdfLogicHeight.value = baseViewport.height const viewport = page.getViewport({ scale: (800 / pdfLogicWidth.value) }) const canvas = document.createElement('canvas') canvas.width = viewport.width canvas.height = viewport.height const ctx = canvas.getContext('2d')! pdfContainer.value.appendChild(canvas) await page.render({ canvasContext: ctx, viewport, canvas }).promise if (props.highlight) drawHighlight(props.highlight) } catch (err) { console.error('Failed to load PDF:', err) } } /* ---------------- 高亮 + 自动滚动 ---------------- */ function drawHighlight(rect: { x0: number; y0: number; x1: number; y1: number }) { const id = activeTab.value const container = pdfContainerMap.get(id) const scrollContainer = container?.parentElement as HTMLDivElement if (!container || !scrollContainer) return // 清除旧高亮 container.querySelectorAll('.pdf-highlight').forEach(el => el.remove()) const canvas = container.querySelector('canvas') if (!canvas) return const canvasScale = parseFloat(canvas.dataset.scale || '1') const baseViewport = tabBaseViewport.get(id) if (!baseViewport) return // 将 PDF 原始坐标 → canvas 渲染坐标 const sx = canvas.width / baseViewport.width const sy = canvas.height / baseViewport.height const hlLeft = rect.x0 * sx const hlTop = rect.y0 * sy const hlWidth = (rect.x1 - rect.x0) * sx const hlHeight = (rect.y1 - rect.y0) * sy const div = document.createElement('div') div.className = 'pdf-highlight' Object.assign(div.style, { position: 'absolute', left: hlLeft + 'px', top: hlTop + 'px', width: hlWidth + 'px', height: hlHeight + 'px', background: 'rgba(0,255,0,0.4)', border: '2px dashed #ff4d4f', pointerEvents: 'none', zIndex: 9999 }) container.style.position = 'relative' container.appendChild(div) /* 自动滚动 */ nextTick(() => { const contW = scrollContainer.clientWidth const contH = scrollContainer.clientHeight scrollContainer.scrollTo({ left: Math.max(0, hlLeft + hlWidth / 2 - contW / 2), top: Math.max(0, hlTop + hlHeight / 2 - contH / 2), behavior: 'smooth' }) }) } /* ---------------- 拖拽 PDF 内容 ---------------- */ let pdfDragging = false let pdfDragStart = { x: 0, y: 0 } let scrollStart = { left: 0, top: 0 } function onPdfMouseDown(e: MouseEvent) { const container = scrollRef.value if (!container) return // 只有在放大时才允许拖动 const scale = getScale(activeTab.value) if (scale <= 1) return // 检查是否点击在高亮区域上 const target = e.target as HTMLElement if (target.classList.contains('pdf-highlight')) return pdfDragging = true pdfDragStart = { x: e.clientX, y: e.clientY } scrollStart = { left: container.scrollLeft, top: container.scrollTop } document.addEventListener('mousemove', onPdfMouseMove) document.addEventListener('mouseup', onPdfMouseUp) container.style.cursor = 'grabbing' e.preventDefault() } function onPdfMouseMove(e: MouseEvent) { if (!pdfDragging) return const dx = e.clientX - pdfDragStart.x const dy = e.clientY - pdfDragStart.y const container = scrollRef.value if (container) { container.scrollTo({ left: scrollStart.left - dx, top: scrollStart.top - dy }) } } function onPdfMouseUp() { pdfDragging = false document.removeEventListener('mousemove', onPdfMouseMove) document.removeEventListener('mouseup', onPdfMouseUp) if (scrollRef.value) { scrollRef.value.style.cursor = 'default' } } /* ---------------- 监听 visible ---------------- */ watch( () => props.visible, async (v, ov) => { if (!v) { return } await nextTick() if (ov === false) { const cw = window.innerWidth const ch = window.innerHeight const mw = 800 const mh = 600 modalStyle.left = (cw - mw) / 2 + 'px' modalStyle.top = (ch - mh) / 2 + 'px' } renderPDF() }, { immediate: true } ) /* ---------------- 关闭 ---------------- */ function close() { pdfRealSize.value = { width: 0, height: 0 } emit('update:visible', false) } /* ---------------- 监听 highlight ---------------- */ watch( () => props.highlight, (newVal) => { if (newVal) drawHighlight(newVal) }, { immediate: false } ) watch(activeTab, (id) => { const tab = tabs.value.find(t => t.id === id) if (!tab) return emit('tabschange', id) renderSinglePDF(tab.url, pdfContainerMap.get(id)!) }) function drawHighlightByCoords(rect: { x0: number; y0: number; x1: number; y1: number }) { drawHighlight(rect) } onMounted(async () => { await nextTick(); const scrollContainer = scrollRef.value; if (scrollContainer) { scrollContainer.addEventListener('mousedown', onPdfMouseDown); } }); defineExpose({ openWithTabs, drawHighlight: drawHighlightByCoords }) </script> <style scoped> .pdf-mask { position: fixed; inset: 0; z-index: 9999; pointer-events: none; } .pdf-modal { position: absolute; background: #fff; border: 1px solid #ccc; border-radius: 4px; box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15); display: flex; flex-direction: column; pointer-events: auto; min-width: 400px; min-height: 300px; resize: both; overflow: hidden; } .pdf-header { display: flex; justify-content: space-between; align-items: center; padding: 8px 12px; background: #f5f5f5; cursor: grab; border-bottom: 1px solid #e4e4e4; user-select: none; } .pdf-header:active { cursor: grabbing; } .pdf-body { flex: none; overflow: auto; cursor: grab; } .pdf-body:active { cursor: grabbing; } .pdf-body canvas { pointer-events: none; } .pdf-modal { height: 700px; } </style>
08-27
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值