2021-06-25

linux驅動管理利器,詳解dkms工作原理 原文網址:https://kknews.cc/code/x6ay3yo.html

 

背景

最近遇到一個麻煩的問題,幾台伺服器在升級內核之後,新的內核啟動直接panic,無法正常驅動raid卡. 通過進入ramos,發現新的內核裡面沒有對應的raid卡驅動ko文件. 後面才發現老的內核中rraid卡驅動是從原始碼單獨編譯出來的. 換了新的kernel之後,忘了給新的內核重新編譯這個驅動就重啟了,導致kernel panic.

在linux中,每個驅動ko文件是與一個特定內核綁定的,也就是在老內核下面編譯的驅動在新內核下面無法使用,同樣的,在新內核下面編譯的驅動在老內核也無法使用.

在linux kernel原始碼樹中,已經包含了我們常見的很多驅動,那麼在安裝一個新的內核rpm包的時候,這些驅動也就被一起安裝了. 但是有些驅動不在原始碼樹裡面,這些驅動需要手動編譯安裝;還有一些驅動雖然在原始碼樹中,但是原始碼樹中的可能太老了,新的驅動也需要單獨編譯安裝.

使用dkms來管理原始碼樹外的驅動

dkms就是用來處理在kernel原始碼樹中不存在的驅動,或者我們自己維護的驅動的. 它可以保證新的kernel安裝之後,這些驅動被自動重新編譯,自動運行depmod命令,自動生成新的initram文件.

dkms這個工具存在於epel源中,需要先保證epel打開,然後yum -y install dkms就可以安裝這個工具了.使用rpm -ql dkms看下這個包安裝了什麼東西.

/usr/sbin/dkms是dkms命令行本身,是一個bash腳本.我們用的最多的是dkms status,用來查看當前dkms管理的驅動的狀態

/var/lib/dkms是dkms工作目錄.

/etc/kernel/目錄下面的兩個文件,分別是postinst和prerm的kernel hook,dkms自動管理驅動核心就是這兩個文件.

現在大部分驅動rpm包都有兩種形式的,一種是直接針對rhel特定版本(也就是特定內核)編譯好的. 還有就是一個dkms enabled的rpm包.

下面以megaraid_sas-07.710.06.00-1dkms.noarch.rpm這個dkms enabled包為例來操作.什麼是dkms enabled的rpm包呢

首先,這個rpm安裝文件的路徑一定是/usr/src/module-version目錄,比如這兒的/usr/src/megaraid_sas-07.710.06.00,然後這個包裡面一定有一個dkms.conf文件.

因為dkms工作的時候需要讀取這個dkms.conf文件,這個文件實際是一個shell腳本,dkms工作的時候會source進去.

我們看下安裝這個包的時候具體會做的事情

安裝之前,dkms status輸出為空,表示沒有管理任何驅動

這個包安裝的時候,會看到dkms add,dkms build(編譯驅動),dkms install(把編譯好的驅動安裝到/lib/modules/`uname -r`目錄下),跑depmod,跑dracut重新生成initramfs的過程(如果手動裝驅動,就是這些過程)

dkms enabled包的scripts,裡面是核心的dkms命令
dkms enabled包的安裝過程,能看到關鍵的步驟被執行

如果dkms只是能幫我們執行這些命令,那麼其實用處也不大. dkms最主要的用處是當我們安裝了一個新的kernel包之後,其會用到kernel hook給我們自動重新給新內核完成上面的事情.

dkms使用kernel hook自動管理驅動

在我們安裝了這個dkms enabled的驅動包之後,我們安裝一個新的內核

能看到這個新的kernel包安裝的過程中跑了一些腳本.

完成之後,dkms status直接顯示新的內核直接把這個驅動包安裝上了.

dkms的工作目錄/var/lib/dkms也顯示這個驅動包安裝到了新的內核.

那麼dkms在安裝新的內核包的時候,怎麼觸發給新的內核包重新編譯驅動的呢?

我們看下內核包的scripts,發現裡面有對/usr/sbin/new-kernel-pkg腳本的調用

而/usr/sbin/new-kernel-pkg裡面有對/etc/kernel/postinst.d/目錄下的腳本進行調用的邏輯.

dkms給/etc/kernel/postinst.d下面安裝了腳本

這個腳本裡面調用/usr/lib/dkms/dkms_autoinstaller進行自動安裝

dkms_autoinstaller調用dkms autoinstall,在autoinstall中,會對當前所有狀態為installed,並且dkms.conf配置匯總AUTOINSTALL=yes的模塊對安裝的內核進行編譯安裝.

總結

dkms是dell做的一個自動管理kernel原始碼樹外的驅動框架,現在大量用於各種硬體,特別是存儲硬體的驅動管理中. 其使用方法特別簡單,只需要將一個dkms enabled的原始碼包安裝即可. 後面的事情就是dkms自己處理了. 大家找硬體驅動包的時候可以找dkms enabled的,如果沒有也可以從原始碼用tarball的方式導入,自己再加個dkms.conf就可以了.



原文網址:https://kknews.cc/code/x6ay3yo.html

2021-06-24

Ubuntu – TP-Link Archer T3U Plus USB adapter

 Cannot make this wireless adapter work on Ubuntu 20.04… please help.

I've tried this: TP-LINK Archer T3U not working in Ubuntu 18.04
and now lsusb sees the device, but still doesn't work.

lsusb:
Bus 002 Device 006: ID 2357:0138 TP-Link 802.11ac NIC

uname -r:
5.4.0-39-generic

Edit: It works on my other computer with Ubuntu 18.04 using T2U Plus driver from here: https://github.com/RinCat/RTL88x2BU-Linux-Driver

But it does not work with Ubuntu 20.04…

sudo modprobe 88x2bu: Gives nothing.

modinfo 88x2bu | grep -e version -e 0138 :

version:        v5.6.1_30362.20181109_COEX20180928-6a6a
srcversion:     9C5282368F93050B7206C8D
parm:           rtw_chip_version:int

dmesg | grep 88×2 :

[ 8087.334869] 88x2bu: loading out-of-tree module taints kernel.
[ 8087.336610] 88x2bu: module verification failed: signature and/or required key missing - tainting kernel
[ 8087.341389] usbcore: registered new interface driver rtl88x2bu

Best Answer

  • Please notice at the git repository that you linked:

    enter image description here

    Your son is evidently using the latest (5 or less days ago) version and you are not.

    Since dkms status returned nothing, we conclude that you installed with sudo make install. Let's remove the inoperative driver, update it and reinstall:

    cd ~/RTL88x2BU-Linux-Driver/
    sudo make uninstall
    make clean
    git pull
    make
    sudo make install
    sudo modprobe 88x2bu
    

    After each kernel update, you must recompile:

    cd ~/RTL88x2BU-Linux-Driver/
    make clean
    git pull
    make
    sudo make install
    sudo modprobe 88x2bu
    

    EDIT: As we have seen, the git pull step was ineffective as you downloaded the zip file rather than obtaining the driver by git clone.

    Please do:

    cd ~/RTL88x2BU-Linux-Driver-master
    

    Or whatever the name of the previously downloaded and extracted driver file is. Next:

    sudo make uninstall
    cd ..
    sudo rm -r RTL88x2BU-Linux-Driver-master
    

    Now. we'll download a fresh copy with updates that drive your device. With a temporary working internet connection, please do:

    wget https://github.com/RinCat/RTL88x2BU-Linux-Driver/archive/master.zip
    unzip master.zip
    cd RTL88x2BU-Linux-Driver-master
    make
    sudo make install
    sudo modprobe 88x2bu
  • 2021-05-06

    Get Volume Path from Drive Name using Powershell script

     Get Volume Path from Drive Name using Powershell script

    November 11, 2014 by Morgan

    I have been working with file access events in Clustered File Server. In event log, for every file access, we are getting 4663 and 4656 events with file path like: DeviceHarddiskVolume4MySharetest.txt instead of my local path F:MySharetest.txt. After I have analyzed , found the prefix path DeviceHarddiskVolume4 is mapped with the device F:. Finally, I found this link (http://poshcode.org/4768), it contains powershell script to list Device Name (Drive letter) and mapped Device Path (Volume Path).

    List Device Name and Volume Path

    Get Volume Path from Device Name (Drive Letter)

    List Device Name and Volume Path using Powershell script

    We can get the Volume Path from Device Name using the Kernel32 module function QueryDosDevice and we can list the available device names (drive letter) using WMI class Win32_Volume. Use the the below Powershell script to list device path and device name.


       1. Copy the below Powershell script and paste in Notepad file.

       2. Save As the Notepad file with the extension .ps1 like List-Device-Name-and-Path.ps1


    Powershell Script: Download List-Device-Name-and-Path.ps1


    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    # Biuild System Assembly in order to call Kernel32:QueryDosDevice. 

       $DynAssembly = New-Object System.Reflection.AssemblyName('SysUtils')

       $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)

       $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('SysUtils', $False)

     

       # Define [Kernel32]::QueryDosDevice method

       $TypeBuilder = $ModuleBuilder.DefineType('Kernel32', 'Public, Class')

       $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('QueryDosDevice', 'kernel32.dll', ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [UInt32], [Type[]]@([String], [Text.StringBuilder], [UInt32]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)

       $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))

       $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')

       $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('kernel32.dll'), [Reflection.FieldInfo[]]@($SetLastError), @($true))

       $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

       $Kernel32 = $TypeBuilder.CreateType()

     

       $Max = 65536

       $StringBuilder = New-Object System.Text.StringBuilder($Max)

     

       Get-WmiObject Win32_Volume | ? { $_.DriveLetter } | % {

           $ReturnLength = $Kernel32::QueryDosDevice($_.DriveLetter, $StringBuilder, $Max)

     

           if ($ReturnLength)

           {

               $DriveMapping = @{

                   DriveLetter = $_.DriveLetter

                   DevicePath = $StringBuilder.ToString()

               }

     

               New-Object PSObject -Property $DriveMapping

           }

       }

       3. Now run the script file List-Device-Name-and-Path.ps1 from Powershell to list all the Device Name and Volume Path in local machine.



    Get Device Path from Device Name using Powershell script

    Get Device Path from Device Name (Drive Letter) using Powershell script

    The below script displays Device Path for the given Device Name(Derive letter). The device name(drive letter) cannot have a trailing backslash; for example, use “C:“, not “C:“. Follow the below steps to get volume path from drive letter.


       1. Copy the below Powershell script and paste in Notepad file.

       2. Save As the Notepad file with the extension .ps1 like Get-DevicePath-from-DeviceName.ps1


    Powershell Script: Download Get-DevicePath-from-DeviceName.ps1


    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    $driveLetter = Read-Host "Enter Drive Letter:"

    Write-Host " "

    $DynAssembly = New-Object System.Reflection.AssemblyName('SysUtils')

    $AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)

    $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('SysUtils', $False)

     

    # Define [Kernel32]::QueryDosDevice method

    $TypeBuilder = $ModuleBuilder.DefineType('Kernel32', 'Public, Class')

    $PInvokeMethod = $TypeBuilder.DefinePInvokeMethod('QueryDosDevice', 'kernel32.dll', ([Reflection.MethodAttributes]::Public -bor [Reflection.MethodAttributes]::Static), [Reflection.CallingConventions]::Standard, [UInt32], [Type[]]@([String], [Text.StringBuilder], [UInt32]), [Runtime.InteropServices.CallingConvention]::Winapi, [Runtime.InteropServices.CharSet]::Auto)

    $DllImportConstructor = [Runtime.InteropServices.DllImportAttribute].GetConstructor(@([String]))

    $SetLastError = [Runtime.InteropServices.DllImportAttribute].GetField('SetLastError')

    $SetLastErrorCustomAttribute = New-Object Reflection.Emit.CustomAttributeBuilder($DllImportConstructor, @('kernel32.dll'), [Reflection.FieldInfo[]]@($SetLastError), @($true))

    $PInvokeMethod.SetCustomAttribute($SetLastErrorCustomAttribute)

    $Kernel32 = $TypeBuilder.CreateType()

     

    $Max = 65536

    $StringBuilder = New-Object System.Text.StringBuilder($Max)

    $ReturnLength = $Kernel32::QueryDosDevice($driveLetter, $StringBuilder, $Max)

     

     if ($ReturnLength)

     {

         Write-Host "Device Path: "$StringBuilder.ToString()

      }

      else

      {

          Write-Host "Device Path: not found"

      }

    Write-Host " "

       3. Now run the script file Get-DevicePath-from-DeviceName.ps1 from Powershell, and give the Drive letter as argument which you want to get device path.



    2021-03-13

    win10 關閉螢幕/無法休眠

     用管理員權限打開CMD或POWERSHELL powercfg /requests 會列出是甚麼東西阻止電源管理進入休眠/關閉螢幕

    MoUsoCoreWorker.exe ...又是windows update

    1. 更新出問題導致這個東西卡在那邊

    可以試著重開機

    然後打開windows update手動檢查

    可能會出現更新

    沒先重開的話是不會出現的 因為已經卡住了

    2.從服務停用windows update然後再啟用

    win+R 輸入 services.msc

    開始>右鍵>電腦管理>服務與應用程式>服務

    找到windows update

    停用

    等個幾分鐘

    啟用


    再次用powercfg /requests檢查, MoUsoCoreWorker.exe應該會消失


    3.強制覆寫

    用這個指令

    powercfg /requestsoverride process MoUsoCoreWorker.exe

    把這個程式排除


    ====== 另外一種被自動喚醒的情形 =====================================

    Windows 10 May 2019 update (1903) breaking sleep

    解法 : gpedit.msc 本機電腦 原則 -> 電腦設定 -> 系統管理範本 ->windows 元件 ->Windows Update ->啟用 Windows Update 電源管理以自動喚醒系統安裝排程的更新 [尚未設定]->[已停用]


    網卡 電源管理 允許這個裝置喚醒電腦 -> disable

    2021-01-30

    不小心, 又把 WZR-HP-G300N 刷壞了

    原本是 OpenWRT, 不小心直接刷 DD-WRT, 導致無法開機 

    bootloader 沒壞

    使用刷 WZR-HP-G450H 的方式救回 

    https://digi9.net/221/openwrt-fix-loi-firmware-router-buffalo-wzr-hp-g450h/#.XDI5XIxR3tQ

    2020-12-30

    QT進階之路 : 布局詳解

     Qt布局管理手冊:<https://kknews.cc/zh-tw/code/2nqnnn9.html>

    http://doc.qt.io/qt-5/qtwidgets-index.html#styles

    http://doc.qt.io/qt-5/qtwidgets-index.html#widgets

    http://doc.qt.io/qt-5/qtwidgets-index.html#layouts

    以下是Qt手冊中的《布局管理》的譯文:

    在一個Widget中,Qt布局管理系統提供了一個簡單而有效的方式來自動組織子widget,以保證他們能夠很好地利用可用空間。

    介紹:

    Qt包含一個布局管理類的集合,它們被用來描述widgets如何在應用程式的用戶介面中呈現的。當可用空間發生變化時,這些布局將自動調整widgets的位置和大小,以確保它們布局的一致性和用戶介面主體可用。

    所有QWidget的子類都可以用布局來管理它們的子類。QWidget::setLayout()函數給widget提供一個布局。當布局通過這種方式設置到widget,它將負責以下任務:

    1.子widget的定位

    2.窗口的合理默認空間

    3.窗口的合理最小空間

    4.調整大小處理

    5.當內容發生變化時自動調整

    6.字體、大小或者內容變化

    7.顯示或 隱藏widget

    8.移除子widget

    Qt的布局類:

    QGraphicsAnchorLayout

    Layout where one can anchor widgets together in Graphics View

    在製圖視圖中布局widget

    QGraphicsAnchor

    Represents an anchor between two items in a QGraphicsAnchorLayout

    表示一個QGraphicsAnchorLayout中兩個圖元之間的anchor

    QBoxLayout

    Lines up child widgets horizontally or vertically

    水平或垂直整理子widget

    QHBoxLayout

    Lines up widgets horizontally

    水平整理子控制項

    QVBoxLayout

    Lines up widgets vertically

    垂直整理子控制項

    QFormLayout

    Manages forms of input widgets and their associated labels

    label-inputwidget模式表單布局

    QGridLayout

    Lays out widgets in a grid

    網格布局

    QLayout

    The base class of geometry managers

    布局,幾何管理的基類

    QLayoutItem

    Abstract item that a QLayout manipulates

    管理的抽象元素

    QSpacerItem

    Blank space in a layout

    空白區域布局

    QWidgetItem

    Layout item that represents a widget

    布局元素

    QSizePolicy

    Layout attribute describing horizontal and vertical resizing policy

    大小策略

    QStackedLayout

    Stack of widgets where only one widget is visible at a time

    棧模式布局,一次只顯示一個

    QButtonGroup

    Container to organize groups of button widgets

    管理按鈕的容器

    QGroupBox

    Group box frame with a title

    帶標題的組箱框架

    QStackedWidget

    Stack of widgets where only one widget is visible at a time

    棧模式的widget,一次只顯示一個

    水平、垂直、網格和表格布局:

    Qt布局類之間的關係如圖1所示:

    圖1 Qt布局類之間的關係

    給widgets一個很好布局的最好方式是使用內置的布局管理器: QHBoxLayout, QVBoxLayout, QGridLayout, and QFormLayout. 這些類都從QLayout繼承而來,它們都來源於QObject(而不是QWidget)。創建更加複雜的布局,可以讓它們彼此嵌套完成。

    QHBoxLayout:水平布局

    QVBoxLayout:垂直布局

    QGridLayout: 表格布局

    QGridLayout::addWidget()語法

    layout->addWidget(widget, row, column, rowSpan, columnSpan);

    參數widget:為插入到這個布局的子控制項;

    參數(row,column)為控制項占據的左上角單元格位置;

    參數rowSpan是控制項占據的行數,

    參數colunmSpan是控制項占據的列的個數。

    (rowSpan和colunmSpan默認值為1)

    Stacked Layouts:分組布局

    QStackedLayout類把子控制項進行分組或者分頁,一次只顯示一組或者一頁,隱藏其他組或者頁上的控制項。

    使用這些Qt布局管理類的另一個原因是,在程序、系統改變字體,語言或者在不同的平台上運行時,布局管理器能夠自動調整窗體里所有控制項的大小和尺寸。

    其他可進行布局管理的類:這些類的共同特點是提供了更加靈活的布局管理,在一定程度上用戶能夠控制窗體內控制項的大小。

    QSplitter,QScrollArea,QMainWindow,QWorkspace(對多文檔的支持)

    2) 布局管理中結合控制項的sizePolicy屬性,進行調整

    結合控制項的SizePolicy屬性,來控制布局管理中的控制項的尺寸自適應方式。

    控制項的sizePolicy說明控制項在布局管理中的縮放方式。Qt提供的控制項都有一個合理的預設sizePolicy,但是這個預設值有時不能適合所有的布局,開發人員經常需要改變窗體上的某些控制項的sizePolicy。一個QSizePolicy的所有變量對水平方向和垂直方向都適用。下面列舉了一些最長用的值:

    A. Fixed:控制項不能放大或者縮小,控制項的大小就是它的sizeHint。

    B. Minimum:控制項的sizeHint為控制項的最小尺寸。控制項不能小於這個sizeHint,但是可以

    放大。

    C. Maximum:控制項的sizeHint為控制項的最大尺寸,控制項不能放大,但是可以縮小到它的最小

    的允許尺寸。

    D. Preferred:控制項的sizeHint是它的sizeHint,但是可以放大或者縮小

    E. Expandint:控制項可以自行增大或者縮小

    註:sizeHint(布局管理中的控制項默認尺寸,如果控制項不在布局管理中就為無效的值)

    1.QHBoxLayout是水平布局,將從左往右(orright to left for right-to-left languages )widget布局成水平行

    2.QVBoxLayout是垂直布局,從頂部到底部

    3.QGridLayout 是二位的網格布局。它可以容納多個單元格:

    4.QFormLayout是兩列label-field式的表單布局

    代碼舉例:

    下面代碼創建QHBoxLayout來管理5個QPushButtons的幾何圖形:

    QWidget *window= new QWidget;
    QPushButton *button1= new QPushButton("One");
    QPushButton *button2= new QPushButton("Two");
    QPushButton *button3= new QPushButton("Three");
    QPushButton *button4= new QPushButton("Four");
    QPushButton *button5= new QPushButton("Five");
    QHBoxLayout *layout= new QHBoxLayout;
    layout->addWidget(button1);
    layout->addWidget(button2);
    layout->addWidget(button3);
    layout->addWidget(button4);
    layout->addWidget(button5);
    window->setLayout(layout);
    window->show();
    

    QGridLayout示例如下:

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QPushButton *button2 = new QPushButton("Two");
     QPushButton *button3 = new QPushButton("Three");
     QPushButton *button4 = new QPushButton("Four");
     QPushButton *button5 = new QPushButton("Five");
     
     QGridLayout *layout = new QGridLayout;
     layout->addWidget(button1, 0, 0);
     layout->addWidget(button2, 0, 1);
     layout->addWidget(button3, 1, 0, 1, 2);
     layout->addWidget(button4, 2, 0);
     layout->addWidget(button5, 2, 1);
     
     window->setLayout(layout);
     window->show();
    

    QFormLayout示例如下:

     QWidget *window = new QWidget;
     QPushButton *button1 = new QPushButton("One");
     QLineEdit *lineEdit1 = new QLineEdit();
     QPushButton *button2 = new QPushButton("Two");
     QLineEdit *lineEdit2 = new QLineEdit();
     QPushButton *button3 = new QPushButton("Three");
     QLineEdit *lineEdit3 = new QLineEdit();
     
     QFormLayout *layout = new QFormLayout;
     layout->addRow(button1, lineEdit1);
     layout->addRow(button2, lineEdit2);
     layout->addRow(button3, lineEdit3);
     
     window->setLayout(layout);
     window->show();
    

    布局技巧:

    當使用布局的時候,在創建子widget時,沒必要給它傳遞父類。布局會自動重新定義它們的父類(通過QWidget::setParent())以確保它們是裝載布局的widget的子類。

    注意1:布局中的控制項是裝載布局控制項的子控制項,不是布局的子控制項。控制項只能以其他控制項作為父類,不可以以布局作為父類。在布局上,可以使用addLayout來嵌套布局;被嵌套的布局,將變成上層布局的子布局。

    向布局添加widgets:

    添加布局到widgets時,布局過程執行如下:

    1.所有widgets將根據它們的 QWidget::sizePolicy() and QWidget::sizeHint()首先分配一些空間。

    2. 如果有widgets設置了大於0的拉伸係數,接下來它們將按照拉伸係數的比例來分配空間。

    3. 如果有widgets設置的拉伸係數是0,它將在沒有其他widgets需要空間時獲取更多空間。其中,帶Expanding大小策略的widget將首先獲得空間。

    4. 所有分配了小於最小空間(或者設置了最小的sizehint)的widget將按要求分配最小空間。(在拉伸係數成為決定因子時,widgets沒必要再用最小值或者最小hint)。

    5. 任何分配了大於最大空間的widget將按要求分配最大空間。(拉伸係數起著決定作用)

    拉伸係數:

    通常,widgets創建的時候沒有設置拉伸係數。當widget整理到一個布局中時,它們將根據QWidget::sizePolicy()或者最小大小hint(取決於誰更大)分配一定空間。拉伸係數被用於按比例改變widget的分配空間。

    如果3個widget用QHBoxLayout 來布局,不帶拉伸係數,它們將得到像下面的布局:

    如果帶上拉伸係數,情況將變成這樣:

    自定義widget的布局:

    當編寫自定義widget類時,需要顯示提供它的布局屬性。如果widget有Qt自帶的布局,它能夠自己滿足自己。如果沒有任何子布局,或者使用手動布局,可以通過下面的機制來改變widget的行為:

    1.實現QWidget::sizeHint() 來返回首先大小

    2.實現QWidget::minimumSizeHint()來返回widget可以擁有的最小空間

    3.調用QWidget::setSizePolicy來描述widget所需的空間

    當size hint、minimum size或size policy改變時,調用QWidget::updateGeometry()。這將促使布局重新進行計算。連續多次調用QWidget::updateGeometry()只會發生一次布局重新計算。

    即便實現了QWidget::heightForWidth(),也有必要提供合理的sizeHint()。

    進一步了解,參見:Trading Height for Width.

    布局問題:

    The use of rich text in a label widget can introduce some problemsto the layout of its parent widget. Problems occur due to the way rich text ishandled by Qt's layout managers when the label is word wrapped.

    Incertain cases the parent layout is put into QLayout::FreeResize mode, meaningthat it will not adapt the layout of its contents to fit inside small sizedwindows, or even prevent the user from making the window too small to beusable. This can be overcome by subclassing the problematic widgets, andimplementing suitable sizeHint() andminimumSizeHint() functions.

    Insome cases, it is relevant when a layout is added to a widget. When you set thewidget of a QDockWidget ora QScrollArea (with QDockWidget::setWidget() andQScrollArea::setWidget()), the layout mustalready have been set on the widget. If not, the widget will not be visible.

    在QLabel中使用富文本會給布局的父類widget帶來一些問題。問題發生的原因是因為當label被文字環繞時,富文本被Qt的布局管理器控制。

    在某些情況下,父類布局被放入QLayout::FreeResize模式,這意味著它將不適應內容布局所設置的最小窗口,或者甚至阻止用戶讓窗口小到不可用的情況。這個可以通過將問題控制項作為子類來解決,並實現合適的sizeHint()和minimumSizeHint()函數。

    在一些情況下,當布局被添加到widget時需要特別注意。當設置QDockWidget ora QScrollArea widget時(用QDockWidget::setWidget() andQScrollArea::setWidget()),布局必須已經被設置到widget上。否則,這些widget將不可見。

    手動布局:

    如果想自定義一個獨特的布局,可以按 如上所述地自定義一個widget。實現QWidget::resizeEvent()來計算所需的大小分配並在每個子類中調用setGeometry() 。

    需要布局需要重新計算大小時,widget將提供一個事件接口QEvent::LayoutRequest 。實現QWidget::event()來接收QEvent::LayoutRequest事件。

    自定義布局管理:

    自定義布局的唯一方法是繼承QLayout來完成自己布局管理器。Border Layout 和Flow Layout 例子將說明如何來完成。

    下面將舉個例子來說明。CardLayout 類,受同名java布局管理的啟發。它分層管理每個元素,每個元素的通過QLayout::spacing()來設置位移量。

    編寫自定義布局類,必須定義以下內容:

    由布局控制的存放元素的數據結構。每個元素都是一個QLayoutItem。在這個例子中,我們將使用QList 。

    1. addItem(),描述如何添加元素到布局。

    2.setGeometry(),描述如何完成布局

    3.sizeHint(),布局的首選大小

    4.itemAt(),描述如何遞歸布局

    5.takeAt(),描述如何移除布局中的元素。

    在大多數情況下,還需要實現minimumSize()。

    頭文件
     
    card.h
    #ifndef CARD_H
    #define CARD_H
     
    #include <QtGui>
    #include <QList>
     
    class CardLayout : public QLayout
    {
    public:
     CardLayout(QWidget *parent, int dist): QLayout(parent, 0, dist) {}
     CardLayout(QLayout *parent, int dist): QLayout(parent, dist) {}
     CardLayout(int dist): QLayout(dist) {}
     ~CardLayout();
     
     void addItem(QLayoutItem *item);
     QSize sizeHint() const;
     QSize minimumSize() const;
     int count() const;
     QLayoutItem *itemAt(int) const;
     QLayoutItem *takeAt(int);
     void setGeometry(const QRect &rect);
     
    private:
     QList<QLayoutItem*> list;
    };
    #endif
     
    實現文件
     
    card.cpp
    #include "card.h"
    int CardLayout::count() const
    {
     // QList::size() returns the number of QLayoutItems in the list
     return list.size();
    }
     
    int CardLayout::count() const
    {
     // QList::size() returns the number of QLayoutItems in the list
     return list.size();
    }
     
    int CardLayout::count() const
    {
     // QList::size() returns the number of QLayoutItems in the list
     return list.size();
    }
     
    CardLayout::~CardLayout()
    {
     QLayoutItem *item;
     while ((item = takeAt(0)))
     delete item;
    }
     
    void CardLayout::setGeometry(const QRect &r)
    {
     QLayout::setGeometry(r);
     
     if (list.size() == 0)
     return;
     
     int w = r.width() - (list.count() - 1) * spacing();
     int h = r.height() - (list.count() - 1) * spacing();
     int i = 0;
     while (i < list.size()) {
     QLayoutItem *o = list.at(i);
     QRect geom(r.x() + i * spacing(), r.y() + i * spacing(), w, h);
     o->setGeometry(geom);
     ++i;
     }
    }
    QSize CardLayout::sizeHint() const
    {
     QSize s(0,0);
     int n = list.count();
     if (n > 0)
     s = QSize(100,70); //start with a nice default size
     int i = 0;
     while (i < n) {
     QLayoutItem *o = list.at(i);
     s = s.expandedTo(o->sizeHint());
     ++i;
     }
     return s + n*QSize(spacing(), spacing());
    }
     
    QSize CardLayout::minimumSize() const
    {
     QSize s(0,0);
     int n = list.count();
     int i = 0;
     while (i < n) {
     QLayoutItem *o = list.at(i);
     s = s.expandedTo(o->minimumSize());
     ++i;
     }
     return s + n*QSize(spacing(), spacing());
    }
     
    

    進一步說明:自定義布局沒有控制寬和高。

    忽略了 QLayoutItem::isEmpty(),這意味著布局將把隱藏widget作為可見的。

    對於複雜布局,通過緩存計算將大大提高速度。在那種情況下,實現QLayoutItem::invalidate() 來標記數據是髒數據。

    調用QLayoutItem::sizeHint()等的代價比較大。在通過函數中,需要再次使用,最好將結果保存在本地變量中。

    在同樣函數的同一個元素中,不應該調用兩次 QLayoutItem::setGeometry()。 這個調用將耗費巨大,如果它用幾個子widget,因為布局管理器每次都要做一個完整的布局。替代方法:先計算geometry,然後再設置(這種事情,不僅應該在布局時注意,在實現resizeEvent()時也需要按同樣方法來做)。

    另外:

    作為QLayout的父類,QLayoutItem提供了下列方法,包括繪製和範圍的信息:

    virtual QSize sizeHint() const = 0
    virtual QRect geometry() const = 0
    virtual void invalidate()
    virtual QLayout * layout()
    Qt::Alignment alignment() const 
    

    QLayout提供的信息就比較多了:提供了子頁面、子Layout的添加接口,設置邊界、菜單項等的接口

    virtual void addItem(QLayoutItem *item) = 0
    void addWidget(QWidget*w)
    void setContentsMargins(intleft, int top, int right, int bottom)
    void setMenuBar(QWidget*widget)
    

    QBoxLayout作為QLayout的子類,提供了一些額外的信息:提供元素的拉伸比例,添加空元素等

    void addLayout(QLayout*layout, int stretch = 0)
    void addSpacerItem(QSpacerItem*spacerItem)
    void addSpacing(intsize)
    bool setStretchFactor(QWidget*widget, int stretch)
    bool setStretchFactor(QLayout*layout, int stretch)
    

    介面的繼承情況,在http://doc.qt.io/qt-5/qlayout.html有詳細的介紹,不做額外的介紹。

    使用的過程的例子介紹一些:

    // 設置邊界為0

    QHBoxLayout *pLayout1 = new QHBoxLayout();
    QHBoxLayout *pLayout2 = new QHBoxLayout();
    pLayout1->setMargin(0);
    pLayout2->setMargin(0);
    

    // 設置拉伸比例2:3

    pLayoutMain->addLayout(pLayout1);
    pLayoutMain->addLayout(pLayout2);
    pLayoutMain->setStretch(0, 2);
    pLayoutMain->setStretch(1, 3);
    

    // 底面邊距設0

    int left = 0, right = 0, top = 0, bottom = 0;
    pMainLayout->getContentsMargins(&left, &top, &right, &bottom);
    pMainLayout->setContentsMargins(left, right, top, 0);
    

    // 添加填充彈簧條

    pLayout1->addItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
    

    // 元素設置位置:左對齊,上下居中

    pLabel->setAlignment(Qt::AlignLeft|Qt::AlignVCenter);
    

    // 設置固定高度

    pLabel->setFixedHeight(21);
    

    // 設置固定寬度

    pLabel->setFixedWidth(21);


    原文網址:https://kknews.cc/code/2nqnnn9.html