• 使用 xlue 实现简单 listbox 控件


    基于 XLUE 实现的 listbox 控件
    1. 提供增删查接口,将 obj 作为子控件添加到列表;
    2. 提供 Attach/Detach 方法,可以将子控件的事件转发出来;
    3. 支持滚动条;
    4. 支持鼠标滚轮;


    实现过程中的注意点:
    1. 使用 ItemObjList 表存储 itemObj , ItemObjList 是一个数组,是 listbox 控件的数据模型;
    2. 使用 EventCookieMap 表存储事件回调, Attach 时监听所有 itemObj 的事件,通过 Insert 添加的 itemObj,也要通过 EventCookieMap 表来确定需要监听哪些事件;
    3. 鼠标滚轮的实现,子控件鼠标滚轮事件 RouteToFather ,滚动条鼠标滚轮事件重定向到 listbox 控件上;
    4. 滚动条的实现,调整位置即可



    代码:

    simplelistbox.xml

    <xlue>
        <control class="SimpleListBox">
            <attr_def>
                <attr name="VScrollBarNormalBkgID" type="string" />
                <attr name="VScrollBarHoverBkgID" type="string" />
                <attr name="VScrollBarDownBkgID" type="string" />
            </attr_def>
            <method_def>
                <AttachEvent file="simplelistbox.xml.lua" func="AttachEvent" />
                <DetachEvent file="simplelistbox.xml.lua" func="DetachEvent" />
                <InsertItem file="simplelistbox.xml.lua" func="InsertItem" />
                <RemoveItem file="simplelistbox.xml.lua" func="RemoveItem" />
                <ClearItem file="simplelistbox.xml.lua" func="ClearItem" />
                <GetItem file="simplelistbox.xml.lua" func="GetItem" />
                <GetItemNum file="simplelistbox.xml.lua" func="GetItemNum" />
            </method_def>
            <event_def>
            </event_def>
            <objtemplate>
                <children>
                    <obj id="list.layout" class="LayoutObject">
                        <attr>
                            <left>0</left>
                            <top>0</top>
                            <width>father.width</width>
                            <height>father.height</height>
                            <limitchild>1</limitchild>
                        </attr>
                        <children>
                            <obj id="listbox.layout" class="LayoutObject">
                                <attr>
                                    <left>0</left>
                                    <top>0</top>
                                    <width>father.width-4</width>
                                    <height>0</height>
                                </attr>
                                <eventlist>
                                    <event name="OnMouseWheel" file="simplelistbox.xml.lua" func="ListLayout_OnMouseWheel" />
                                </eventlist>
                            </obj>
                            <obj id="vscrollbar" class="TextureObject">
                                <attr>
                                    <left>father.width-4</left>
                                    <top>0</top>
                                    <width>4</width>
                                    <height>0</height>
                                </attr>
                                <eventlist>
                                    <event name="OnLButtonDown" file="simplelistbox.xml.lua" func="VScrollBar_OnLButtonDown" />
                                    <event name="OnLButtonUp" file="simplelistbox.xml.lua" func="VScrollBar_OnLButtonUp" />
                                    <event name="OnMouseMove" file="simplelistbox.xml.lua" func="VScrollBar_OnMouseMove" />
                                    <event name="OnMouseLeave" file="simplelistbox.xml.lua" func="VScrollBar_OnMouseLeave" />
                                    <event name="OnMouseWheel" file="simplelistbox.xml.lua" func="ListLayout_OnMouseWheel" redirect="father:listbox.layout"/>
                                </eventlist>
                            </obj>
                        </children>
                    </obj>
                </children>
                <eventlist>
                    <event name="OnInitControl" file="simplelistbox.xml.lua" func="OnInitControl" />
                    <event name="OnDestroy" file="simplelistbox.xml.lua" func="OnDestroy" />
                </eventlist>
            </objtemplate>
        </control>
    </xlue>

    simplelistbox.xml.lua

    ------------------ 以下为外部函数 -------------------
    
    function AttachEvent(ctrlObj, eventName, callback)
        if type(eventName) ~= "string" or type(callback) ~= "function" then
            return
        end
        local attr = ctrlObj:GetAttribute()
        if attr.EventCookieMap[eventName] == nil then
            attr.EventCookieMap[eventName] = {}
        end
        local cookie = table.maxn(attr.EventCookieMap[eventName]) + 1
        attr.EventCookieMap[eventName][cookie] = callback
        
        for index,itemObj in ipairs(attr.ItemObjList) do
            local itemcookie,bRet = itemObj:AttachListener(eventName, true, function(...)
                for cookie,callback in pairs(attr.EventCookieMap[eventName]) do
                    callback(index, ...)
                end
            end)
        end
        return cookie
    end
    
    function DetachEvent(ctrlObj, eventName, cookie)
        if type(eventName) ~= "string" or type(cookie) ~= "number" then
            return
        end
        local attr = ctrlObj:GetAttribute()
        if attr.EventCookieMap[eventName] == nil then
            return
        end
        attr.EventCookieMap[eventName][cookie] = nil
    end
    
    function InsertItem(ctrlObj, index, obj)
        if type(index) ~= "number" or obj == nil then
            return
        end
        local attr = ctrlObj:GetAttribute()
        local listLayout = ctrlObj:GetControlObject("listbox.layout")
    
        -- 插入item,调整位置
        listLayout:AddChild(obj)
        local l,t,r,b = obj:GetObjPos()
        local width,height = r-l,b-t
        if index <= 1 or #attr.ItemObjList == 0 then    -- 插入到最前
            for i,itemObj in ipairs(attr.ItemObjList) do
                local l,t,r,b = itemObj:GetObjPos()
                itemObj:SetObjPos(l,t+height,r,b+height)
            end
            obj:SetObjPos(0,0,width,height)
            index = 1
        elseif index > #attr.ItemObjList then    -- 插入到最后
            local lastObj = attr.ItemObjList[#attr.ItemObjList]
            local _,_,_,bottom = lastObj:GetObjPos()
            obj:SetObjPos(0,bottom,width,bottom+height)
            index = #attr.ItemObjList + 1
        else    -- 插入到中间
            local preObj = attr.ItemObjList[index-1]
            local _,_,_,bottom = preObj:GetObjPos()
            for i=index, #attr.ItemObjList do
                local itemObj = attr.ItemObjList[i]
                local l,t,r,b = itemObj:GetObjPos()
                itemObj:SetObjPos(l,t+height,r,b+height)
            end
            obj:SetObjPos(0,bottom,width,bottom+height)
        end
        table.insert(attr.ItemObjList, index, obj)
        
        -- 调整 listLayout 大小
        local lastObj = attr.ItemObjList[#attr.ItemObjList]
        local _,_,_,bottom = lastObj:GetObjPos()
        local l,t,r,b = listLayout:GetObjPos()
        listLayout:SetObjPos(l,t,r,t+bottom)
        
        -- 调整 VScrollBar 位置
        __AdjustVScrollBarPos(ctrlObj)
        
        -- 监听新添加item的事件
        for eventName,cookieTable in pairs(attr.EventCookieMap) do
            if table.maxn(cookieTable) > 0 then
                obj:AttachListener(eventName, true, function(...)
                    for cookie, callback in pairs(cookieTable) do
                        callback(index, ...)
                    end
                end)
            end
        end
        -- 监听OnPosChange,当 obj height 改变时要调整其它 itemObj 的位置
        obj:AttachListener("OnPosChange", true, function(obj,oldLeft,oldTop,oldRight,oldBottom,newLeft,newTop,newRight,newBottom)
            local oldHeight,newHeight = oldBottom-oldTop,newBottom-newTop
            if oldHeight == newHeight then
                return
            end
        
            local index = nil
            for i,itemObj in ipairs(attr.ItemObjList) do
                if itemObj:GetID() == obj:GetID() and obj:GetID() ~= nil then
                    index = i 
                    break
                end
            end
            if not index then
                return
            end
            for i=index+1,#attr.ItemObjList do
                local itemObj = attr.ItemObjList[i]
                local l,t,r,b = itemObj:GetObjPos()
                itemObj:SetObjPos(l,t+newHeight-oldHeight,r,b+newHeight-oldHeight)
            end
            -- 调整 VScrollBar 位置
            __AdjustVScrollBarPos(ctrlObj)
        end)
        -- 监听 OnMouseWheel ,在 itemObj 上的鼠标滚轮事件也要被响应
        obj:AttachListener("OnMouseWheel", true, function(obj, x, y, distance, flags)
            obj:RouteToFather()
        end)
    end
    
    function RemoveItem(ctrlObj, index)
        if type(index) ~= "number" then
            return
        end
        local attr = ctrlObj:GetAttribute()
        local listLayout = ctrlObj:GetControlObject("listbox.layout")
        if #attr.ItemObjList == 0 then
            return
        end
        
        if index <= 1 then
            index = 1
        elseif index > #attr.ItemObjList then
            index = #attr.ItemObjList
        end
        
        -- 删除 obj 调整其它 itemObj 位置
        local obj = attr.ItemObjList[index]
        local l,t,r,b = obj:GetObjPos()
        local width,height = r-l,b-t
        for i=index+1, #attr.ItemObjList do
            local itemObj = attr.ItemObjList[i]
            local l,t,r,b = itemObj:GetObjPos()
            itemObj:SetObjPos(l,t-height,r,b-height)
        end
        listLayout:RemoveChild(obj)
        table.remove(attr.ItemObjList, index)
        
        -- 调整 listLayout 高度
        local bottom = 0
        local lastObj = attr.ItemObjList[#attr.ItemObjList]
        if lastObj then
            _,_,_,bottom = lastObj:GetObjPos()
        end
        local l,t,r,b = listLayout:GetObjPos()
        listLayout:SetObjPos(l,t,r,t+bottom)
        
        -- 调整 VScrollBar 位置
        __AdjustVScrollBarPos(ctrlObj)
    end
    
    function ClearItem(ctrlObj)
        local attr = ctrlObj:GetAttribute()
        local listLayout = ctrlObj:GetControlObject("listbox.layout")
        listLayout:RemoveAllChild()
        attr.ItemObjList = {}
    end
    
    function GetItem(ctrlObj, index)
        if type(index) ~= "number" then
            return
        end
        local attr = ctrlObj:GetAttribute()
        return attr.ItemObjList[index]
    end
    
    function GetItemNum(ctrlObj)
        local attr = ctrlObj:GetAttribute()
        return #attr.ItemObjList
    end
    
    ----------------- 以下为事件处理函数 ------------------
    
    function OnInitControl(ctrlObj)
        local attr = ctrlObj:GetAttribute()
        attr.ItemObjList = {}
        attr.EventCookieMap = {}
        
        local vsbarObj = ctrlObj:GetControlObject("vscrollbar")
        if attr.VScrollBarNormalBkgID then
            vsbarObj:SetTextureID(attr.VScrollBarNormalBkgID)
        end
    end
    
    function OnDestroy(ctrlObj)
    
    end
    
    function ListLayout_OnMouseWheel(obj, x, y, distance, flags)
        local ctrlObj = obj:GetOwnerControl()
        local listLayout = ctrlObj:GetControlObject("listbox.layout")
        local fatherLayout = listLayout:GetParent()
        local left,top,right,bottom = listLayout:GetObjPos()
        local _,fatherTop,_,fatherBottom = fatherLayout:GetObjPos()
        local fatherHeight = fatherBottom-fatherTop
        
        -- distance 为正数时, listLayout 向下移动,反之向上移动
        local moveDistance = distance/10
        if moveDistance > 0 and top + moveDistance > 0 then    -- 向下移动到顶了
            if top < 0 then
                listLayout:SetObjPos(left,0,right,bottom-top)
            end
        elseif moveDistance < 0 and bottom + moveDistance < fatherHeight then    -- 向上移动到底了
            if bottom > fatherHeight then
                listLayout:SetObjPos(left,fatherHeight-(bottom-top),right,fatherHeight)
            end
        else
            listLayout:SetObjPos(left,top+moveDistance,right,bottom+moveDistance)
        end
        __AdjustVScrollBarPos(ctrlObj)
    end
    
    function VScrollBar_OnLButtonDown(vsbarObj)
        vsbarObj:SetCaptureMouse(true)
        local ctrlObj = vsbarObj:GetOwnerControl()
        local attr = ctrlObj:GetAttribute()
        if attr.VScrollBarDownBkgID then
            vsbarObj:SetTextureID(attr.VScrollBarDownBkgID)
        end
    end
    
    function VScrollBar_OnLButtonUp(vsbarObj)
        vsbarObj:SetCaptureMouse(false)
        local ctrlObj = vsbarObj:GetOwnerControl()
        local attr = ctrlObj:GetAttribute()
        if attr.VScrollBarNormalBkgID then
            vsbarObj:SetTextureID(attr.VScrollBarNormalBkgID)
        end
    end
    
    function VScrollBar_OnMouseMove(vsbarObj, x, y, flags)
        if flags == 1 then    -- 鼠标左键被按下
            local listLayout = vsbarObj:GetOwnerControl():GetControlObject("listbox.layout")
            local fatherLayout = vsbarObj:GetParent()
            local _,listTop,_,listBottom = listLayout:GetObjPos()
            local _,fatherTop,_,fatherBottom = fatherLayout:GetObjPos()
            local listHeight,fatherHeight = listBottom-listTop,fatherBottom-fatherTop
            local _,vsbTop,_,vsbBottom = vsbarObj:GetObjPos()
            local vsbHeight = vsbBottom-vsbTop
            
            -- 调整 VScrollBar 的位置
            local vsbTop = y
            if y < 0 then
                vsbTop = 0
            elseif y > fatherHeight - vsbHeight then
                vsbTop = fatherHeight - vsbHeight
            end
            local l,t,r,b = vsbarObj:GetObjPos()
            if t ~= vsbTop then
                vsbarObj:SetObjPos(l,vsbTop,r,vsbTop+vsbHeight)
            end
            
            -- 调整 listLayout 的位置
            local listTop = 0
            listTop = -listHeight * vsbTop/fatherHeight
            local l,t,r,b = listLayout:GetObjPos()
            if t ~= listTop then
                listLayout:SetObjPos(l,listTop,r,listTop+listHeight)
            end
        end
        
        if flags ~= 1 then
            local ctrlObj = vsbarObj:GetOwnerControl()
            local attr = ctrlObj:GetAttribute()
            if attr.VScrollBarHoverBkgID then
                vsbarObj:SetTextureID(attr.VScrollBarHoverBkgID)
            end
        end
    end
    
    function VScrollBar_OnMouseLeave(vsbarObj)
        local ctrlObj = vsbarObj:GetOwnerControl()
        local attr = ctrlObj:GetAttribute()
        if attr.VScrollBarNormalBkgID then
            vsbarObj:SetTextureID(attr.VScrollBarNormalBkgID)
        end
    end
    
    -------------------- 以下为内部函数 -------------------
    
    function __AdjustVScrollBarPos(ctrlObj)
        local listLayout = ctrlObj:GetControlObject("listbox.layout")
        local fatherLayout = listLayout:GetParent()
        local vsbarObj = ctrlObj:GetControlObject("vscrollbar")
        
        local _,listTop,_,listBottom = listLayout:GetObjPos()
        local _,fatherTop,_,fatherBottom = fatherLayout:GetObjPos()
        local listHeight,fatherHeight = listBottom-listTop,fatherBottom-fatherTop
        
        -- 大小
        local vsbHeight = 0
        if listHeight > fatherHeight then
            vsbHeight = fatherHeight * fatherHeight/listHeight 
        end
        -- 位置
        local vsbTop = 0
        if listHeight > fatherHeight then
            vsbTop = fatherHeight * (-listTop)/listHeight
        end
        
        local l,t,r,b = vsbarObj:GetObjPos()
        if t ~= vsbTop or b-t ~= vsbHeight then
            vsbarObj:SetObjPos(l,vsbTop,r,vsbTop+vsbHeight)
        end
    end
  • 相关阅读:
    Go源码文件与命令
    K8s控制器
    odoo 在form视图sheet右上角增加按钮
    odoo 常用widget
    odoo tree视图中实现横向滚动条
    可能是智障的高二生活
    千题计划
    闲谈
    线性代数与simplex
    好题集锦
  • 原文地址:https://www.cnblogs.com/zuibunan/p/3938338.html
Copyright © 2020-2023  润新知