Найти - Пользователи
Полная версия: PyQT hover
Начало » GUI » PyQT hover
1
Edith
Доброго времени суток!
Использую python 3.5 + pyQT5

Создаю окно, внутри него лейблы.
Как мне на лейблы повесить события ховер( и клик заодно)
Например на InfoSover
Если прописываю просто внутри класса
def enterEvent(self, QEvent):
то он срабатывает на все окно, а мне надо конкретно к определенному лейлу/баттону/любому элементу применить

 from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from functions.main_funcs import getScreen
import configparser
class ProfileForm(QWidget):
    # Объявляем позицию окна для последущей записи
    PF_wx = 0
    PF_wy = 0
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool) # окно без рамок и заголовка, не отображается на панели задач
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # прозрачный бек окна
        self.setFixedSize(330, 220)
        # InfoS BG
        info_s = QLabel(self)
        info_s.setStyleSheet("background-repeat: none; background-image: url(.data/img/infoS.png); background-position:0 0;")
        info_s.setFixedSize(303, 220)
        info_s.move(0, 0)
        # user avatar
        avatar = QLabel(self)
        avatar.setStyleSheet("background-repeat: none; background-image: url(.data/img/avatarDemo.png); background-position:center center; border-radius:50px; border: 1px solid transparent;")
        avatar.setFixedSize(103, 103)
        avatar.move(67, 50)
        # gate6 BG
        gate6 = QLabel(self)
        gate6.setStyleSheet("background-repeat: none; background-image: url(.data/img/gate6.png); background-position:0 0;")
        gate6.setFixedSize(180, 90)
        gate6.move(145, 42)
        # InfoSover
        infosover = QLabel(self)
        infosover.setStyleSheet("cursor:pointer; background: transparent; background-repeat: none; background-image: url(.data/img/over.png); background-position:0 0;")
        infosover.setFixedSize(112, 112)
        infosover.move(62, 47)
        # Получаем данные о расположении окна и зписываем в конфиг положение окна если его нет
        config = configparser.ConfigParser()
        config.read('config.ini')
        profile_xy_x = 0
        profile_xy_y = 0
        try:
            profile_xy_x = int(config.get('profileXY','x'))
            profile_xy_y = int(config.get('profileXY', 'y'))
        except configparser.NoSectionError:
            config.add_section('profileXY')
            config.set('profileXY', 'x', '0')
            config.set('profileXY', 'y', '0')
            with open('config.ini', 'w') as configfile:
                config.write(configfile)
        finally:
            self.move(profile_xy_x, profile_xy_y)
        # показываем окно
        self.show()
    # Возможность перемещения окна
    def mousePressEvent(self, event):
        self.offset = event.pos()
    def mouseMoveEvent(self, event):
        global PF_wx, PF_wy
        x = event.globalX()
        y = event.globalY()
        x_w = self.offset.x()
        y_w = self.offset.y()
        w_x = x - x_w
        w_y = y - y_w
        # Вычисляем размеры экрана и не даем окну выходить за его пределы
        if (w_x < 0): w_x = 0
        if (w_y < 0): w_y = 0
        screen = getScreen()
        screen_w = screen[0]
        screen_h = screen[1]
        win_w = self.width()
        win_h = self.height()
        if (w_x > (screen_w - win_w)): w_x = screen_w - win_w
        if (w_y > (screen_h - win_h)): w_y = screen_h - win_h
        PF_wx = w_x
        PF_wy = w_y
        # двигаем окно по позиции
        self.move(w_x, w_y)
    def mouseReleaseEvent(self, event):
        global PF_wx, PF_wy
        # Записываем в конфиг положение окна после перетаскивания
        config = configparser.ConfigParser()
        config.read('config.ini')
        config.set('profileXY', 'x', str(PF_wx))
        config.set('profileXY', 'y', str(PF_wy))
        with open('config.ini', 'w') as configfile:
            config.write(configfile)
    def close(self):
        self.hide()
        qApp.quit()

Сделала такой вариант через баттон:
 from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from PyQt5.QtCore import pyqtSignal
from functions.main_funcs import getScreen
import configparser
class HoverButton(QPushButton):
    mouseHover = pyqtSignal(bool)
    def __init__(self, parent=None):
        QPushButton.__init__(self, parent)
        self.setMouseTracking(True)
    def enterEvent(self, event):
        self.mouseHover.emit(True)
        global infosover
        infosover.setStyleSheet("cursor:pointer; background: transparent; background-repeat: none; background-image: url(.data/img/over.png); background-position:0 0;")
    def leaveEvent(self, event):
        self.mouseHover.emit(False)
        infosover.setStyleSheet("cursor:pointer; background: transparent;")
class ProfileForm(QWidget):
    # Объявляем позицию окна для последущей записи
    PF_wx = 0
    PF_wy = 0
    infosover = 0
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint | QtCore.Qt.Tool) # окно без рамок и заголовка, не отображается на панели задач
        self.setAttribute(QtCore.Qt.WA_TranslucentBackground) # прозрачный бек окна
        self.setFixedSize(330, 220)
        # InfoS BG
        info_s = QLabel(self)
        info_s.setStyleSheet("background-repeat: none; background-image: url(.data/img/infoS.png); background-position:0 0;")
        info_s.setFixedSize(303, 220)
        info_s.move(0, 0)
        # user avatar
        avatar = QLabel(self)
        avatar.setStyleSheet("background-repeat: none; background-image: url(.data/img/avatarDemo.png); background-position:center center; border-radius:50px; border: 1px solid transparent;")
        avatar.setFixedSize(103, 103)
        avatar.move(67, 50)
        # gate6 BG
        gate6 = QLabel(self)
        gate6.setStyleSheet("background-repeat: none; background-image: url(.data/img/gate6.png); background-position:0 0;")
        gate6.setFixedSize(180, 90)
        gate6.move(145, 42)
        # InfoSover
        global infosover
        infosover = HoverButton(self)
        infosover.clicked.connect(self.close)
        infosover.mouseHover.connect(self.hover)
        infosover.setStyleSheet("background: transparent;")
        infosover.setFixedSize(112, 112)
        infosover.move(63, 45)
        # Получаем данные о расположении окна и зписываем в конфиг положение окна если его нет
        config = configparser.ConfigParser()
        config.read('config.ini')
        profile_xy_x = 0
        profile_xy_y = 0
        try:
            profile_xy_x = int(config.get('profileXY','x'))
            profile_xy_y = int(config.get('profileXY', 'y'))
        except configparser.NoSectionError:
            config.add_section('profileXY')
            config.set('profileXY', 'x', '0')
            config.set('profileXY', 'y', '0')
            with open('config.ini', 'w') as configfile:
                config.write(configfile)
        finally:
            self.move(profile_xy_x, profile_xy_y)
        # показываем окно
        self.show()
    def hover(self):
        pass
    # Возможность перемещения окна
    def mousePressEvent(self, event):
        self.offset = event.pos()
    def mouseMoveEvent(self, event):
        global PF_wx, PF_wy
        x = event.globalX()
        y = event.globalY()
        x_w = self.offset.x()
        y_w = self.offset.y()
        w_x = x - x_w
        w_y = y - y_w
        # Вычисляем размеры экрана и не даем окну выходить за его пределы
        if (w_x < 0): w_x = 0
        if (w_y < 0): w_y = 0
        screen = getScreen()
        screen_w = screen[0]
        screen_h = screen[1]
        win_w = self.width()
        win_h = self.height()
        if (w_x > (screen_w - win_w)): w_x = screen_w - win_w
        if (w_y > (screen_h - win_h)): w_y = screen_h - win_h
        PF_wx = w_x
        PF_wy = w_y
        # двигаем окно по позиции
        self.move(w_x, w_y)
    def mouseReleaseEvent(self, event):
        global PF_wx, PF_wy
        # Записываем в конфиг положение окна после перетаскивания
        config = configparser.ConfigParser()
        config.read('config.ini')
        config.set('profileXY', 'x', str(PF_wx))
        config.set('profileXY', 'y', str(PF_wy))
        with open('config.ini', 'w') as configfile:
            config.write(configfile)
    def close(self):
        self.hide()
        qApp.quit()

Работает, но по мне это какой-то костыль на глобалсах, и по идее должно делаться куда проще. Ибо если вешать ховер на другие элементы
мне придется класс class HoverButton постоянно дублировать чтобы задавать события для разных элементов окна.

2) Как можно сменить курсор у лейбла/баттона? в css cursor:hand; не пашет. через setCursor тоже что-то не пойму как сделать)
PEHDOM
Edith
Если прописываю просто внутри класса
def enterEvent(self, QEvent):
то он срабатывает на все окно, а мне надо конкретно к определенному лейлу/баттону/любому элементу применить
1. Создать свой класс наследник и переопределить нужные ивенты.. Собственно вы частично реализвали это.. только накой вам там глобалс? Тем более внутри класса. Если уж нужно чтото хранить, храните как атрибут класса self.PF_wx и self.PF_wy и обращаетсь к ним соответвенно. Кроме того у любого виджета есть pos () , х() и y() которые содержат координаты виджета относительно родителя. так жк есть mapToGlobal() и mapFromGlobal(), может пригодиться.
2. установить eventFilter для дочерних обьектов anyWidget.installEventFilter(otherWdget), и переопределить eventFilter() у otherWdget, тогда дочерние обьекты будут все ивенты направлять в otherWdget , а вы сможете уже в нем фильтровать, если обьект такойто и ивент такойто то делаем тото….

Edith
2) Как можно сменить курсор у лейбла/баттона?

гуглите setCursor(cursor) unsetCursor(), цепляете на QEvent.Enter/QEvent.Leave (ивенты генерируемые виджетами когда кусор входит/покидает область занимаемую виджетом) если используете eventFilter или просто пример:
 cursor = QCursor(Qt.WaitCursor)
widget.setCursor(cursor)
при наведении мышки на widget курсор будет принимать форму песочны часов.
Edith
PEHDOM
2. установить eventFilter для дочерних обьектов anyWidget.installEventFilter(otherWdget), и переопределить eventFilter() у otherWdget, тогда дочерние обьекты будут все ивенты направлять в otherWdget , а вы сможете уже в нем фильтровать, если обьект такойто и ивент такойто то делаем тото….
 from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from functions.main_funcs import getScreen
import configparser
class Filter(QObject):
    def __init__(self):
        super(QObject, self).__init__()
    def eventFilter(self, obj, event):
        print(event.type())
        return False
class ProfileForm(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
    def initUI(self):
        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Tool) # окно без рамок и заголовка, не отображается на панели задач
        self.setAttribute(Qt.WA_TranslucentBackground) # прозрачный бек окна
        self.setFixedSize(330, 220)
        self.filter = Filter()
        # Объявляем позицию окна для последущей записи
        self.PF_wx = 0
        self.PF_wy = 0
        # InfoS BG
        self.info_s = QLabel(self)
        self.info_s.setStyleSheet("background-repeat: none; background-image: url(.data/img/infoS.png); background-position:0 0;")
        self.info_s.setFixedSize(303, 220)
        self.info_s.move(0, 0)
        # user avatar
        self.avatar = QLabel(self)
        self.avatar.setStyleSheet("background-repeat: none; background-image: url(.data/img/avatarDemo.png); background-position:center center; border-radius:50px; border: 1px solid transparent;")
        self.avatar.setFixedSize(103, 103)
        self.avatar.move(67, 50)
        # gate6 BG
        self.gate6 = QLabel(self)
        self.gate6.setStyleSheet("background-repeat: none; background-image: url(.data/img/gate6.png); background-position:0 0;")
        self.gate6.setFixedSize(180, 90)
        self.gate6.move(145, 42)
        # InfoSover
        self.infosover = QLabel(self)
        self.infosover.setStyleSheet("cursor:pointer; background: transparent; background-repeat: none; background-image: url(.data/img/over.png); background-position:0 0;")
        self.infosover.setFixedSize(112, 112)
        self.infosover.move(62, 47)
        self.infosover.setCursor(Qt.PointingHandCursor)
        self.infosover.installEventFilter(self.filter)
        # Получаем данные о расположении окна и зписываем в конфиг положение окна если его нет
        config = configparser.ConfigParser()
        config.read('config.ini')
        profile_xy_x = 0
        profile_xy_y = 0
        try:
            profile_xy_x = int(config.get('profileXY','x'))
            profile_xy_y = int(config.get('profileXY', 'y'))
        except configparser.NoSectionError:
            config.add_section('profileXY')
            config.set('profileXY', 'x', '0')
            config.set('profileXY', 'y', '0')
            with open('config.ini', 'w') as configfile:
                config.write(configfile)
        finally:
            self.move(profile_xy_x, profile_xy_y)
        # показываем окно
        self.show()
    # Возможность перемещения окна
    def mousePressEvent(self, event):
        self.offset = event.pos()
    def mouseMoveEvent(self, event):
        x = event.globalX()
        y = event.globalY()
        x_w = self.offset.x()
        y_w = self.offset.y()
        w_x = x - x_w
        w_y = y - y_w
        # Вычисляем размеры экрана и не даем окну выходить за его пределы
        if (w_x < 0): w_x = 0
        if (w_y < 0): w_y = 0
        screen = getScreen()
        screen_w = screen[0]
        screen_h = screen[1]
        win_w = self.width()
        win_h = self.height()
        if (w_x > (screen_w - win_w)): w_x = screen_w - win_w
        if (w_y > (screen_h - win_h)): w_y = screen_h - win_h
        self.PF_wx = w_x
        self.PF_wy = w_y
        # двигаем окно по позиции
        self.move(w_x, w_y)
    def mouseReleaseEvent(self, event):
        # Записываем в конфиг положение окна после перетаскивания
        config = configparser.ConfigParser()
        config.read('config.ini')
        config.set('profileXY', 'x', str(self.PF_wx))
        config.set('profileXY', 'y', str(self.PF_wy))
        with open('config.ini', 'w') as configfile:
            config.write(configfile)
    def close(self):
        self.hide()
        qApp.quit()

сделала ивент фильтр и теперь можно на все объекты вешать ивенты.
А как теперь повесить колбеки туда
Т.е. там где у меня
     def eventFilter(self, obj, event):
        print(event.type())
        return False
выполнялся колбек например
     def eventFilter(self, obj, event):
        if event.type() == 11: 
            callback1()
        elif event.type() == 10:
            callback2()
а здесь
 self.infosover.installEventFilter(self.filter)
где у меня вызывается фильтр я бы уже задавала, что для infosover у меня такие фунцкции
для допустим gate6 у меня другие функции если я повешу на gate6 ивент фильтр
что-то типа такого
self.infosover.installEventFilter(self.filter(myFunc1, myFunc2, myFunc3))

или как это в javascript делается
 infosover.hover(function(){
// dosomething on mouseIn
}, function(){
// dosomething on mouseOut
});
вот нечто подобное хочу сделать в питоне))
PEHDOM
self.infosover.installEventFilter(self.filter(myFunc1, myFunc2, myFunc3)) оно не так работает.
в installEventFilter вы просто указывате таргет на обьект котороый будет фильтровать ваши иветны.
Не обязательно создавать отдельный класс для этого, это может быть любой обьект наследник QObject.
В иветфильтре у вас есть два параметра, ивент и обьек котороый послал ивент. вот собственно там оно все и фильтруеться по приципу:
      def eventFilter(self, obj, event):
        if event.type() == 11:      # Если мышь покинула область фиджета
            if obj.objectName() == 'gate6':   # И если этот виджет с именем gate6
                  callback1()        # выполнить  callback1() 
        elif event.type() == 10:      # Если мышь над виджетом
           if obj.objectName() == 'infosover': # И если этот виджет c именем  infosover
               callback2()          # выполнить  callback2()
           elif type(obj) == QPushButton:  # Если это любая кнопка
              obj.setIcon(icon)                   # Изменить иконку кнопки на icon

Естественно имена обьектам нужно дать заранее
 self.infosover = QLabel(self)
self.infosover.setObjectName('infosover')

Вобще ивентфильтр не всегда стоит применять. Если вам нужно много однотипных действия для разных виджетов, например одинаковое контекстное меню, то вы фильтруете QEvent.ContextMenu и выводите его, вместо того чтобы переопределять contextMenuEvent у десятка классов виджетов. А если вам нужно какоето особое поведение у каждого виджета, то имхо лучше переопределить методы у каждого типа. Хотя ситуации разные бывают.
Edith
PEHDOM
self.infosover.installEventFilter(self.filter(myFunc1, myFunc2, myFunc3)) оно не так работает.в installEventFilter вы просто указывате таргет на обьект котороый будет фильтровать ваши иветны. Не обязательно создавать отдельный класс для этого, это может быть любой обьект наследник QObject. D иветфильтре у вас есть два параметра, ивент и обьек котороый послал ивент. вот собственно там оно все и фильтруеться по приципу:
Спасибо огромное! Теперь все понятно как работает.
Идеально, то что и нужно было
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB