UAC는 관리자 권한이 없는 사용자가, 사용자 계정 변경이나/로그오프나/RunAs를 사용하지 않고도 관리자 권한의 작업을 하는 것을 허용한다.
UAC Enabled 된 프로그램을 설치하거나 실행하기
일부 프로그램의 설치나 실행 시 administrator access token을 필요로 하기에, 운영체제는 사용자에게 동의를 구하는 창(Consent Prompt)을 띄운다. 사용자가 동의를 하면 어드민 계정으로 전환할 필요 없이 해당 프로그램을 설치/실행할 수 있도록 한다.
어드민 권한이 필요한 작업들
프린터나 특정 하드웨어 설치
특정 어플리케이션 설치 (어드민 권한을 필요로 하는 어플리케이션이나, "C:\Program Files" 등 어드민 권한이 필요한 영역에 어플리케이션을 설치하는 경우)
어드민 권한을 필요로 하는 어플리케이션 실행
시스템 시간 변경
legacy 어플리케이션 실행
인스톨러 만들 때 UAC 설정하기
인스톨러 자체의 실행 권한 설정
NSIS 스크립트로 인스톨러를 만드는 경우, RequestExecutionLevel 로 UAC 레벨을 설정해 줄 수 있다.
RequestExecutionLevel admin # 어드민 권한 필요 - 실행 시 UAC 동의 창이 뜸
RequestExecutionLevel user # 어드민 권한 필요 없음 - 실행 시 UAC 동의 창 뜨지 않음
인스톨(설치) 과정에서 필요한 실행 권한
인스톨러는 user 권한으로 만들었다 하더라도, 사용자가 어드민 권한이 필요한 위치에 프로그램을 설치하려고 하면(C:\Program Files 등) 어드민 권한이 필요하다. 그냥 실행하면 인스톨러는 실행 되나, 파일 복사 등 설치가 되지 않는다.
이떄는, user 권한의 인스톨러를 RunAs를 사용해 어드민 권한으로 실행 하거나
인스톨러 자체를 admin 권한이 필요하도록 만들고, 어드민 권한이 필요 없는 위치에 설치할 경우 RunAsInvoker를 통해 UAC 동의 창을 띄우지 않도록 할 수 있다.
관련 문제 : 레지스트리
프로그램에서 레지스트리에 값을 쓰거나 읽는 경우, HKLM 또는 HKCU를 이용한다. 이 때 어느 레지스트리를 사용하는가에 따라 필요한 사용자 권한이 달라진다.
HKLM(HKEY_LOCAL_MACHINE) : 모든 유저에게 적용되는 레지스트리 => 어드민 권한 필요
HKCU(HKEY_CURRENT_USER) : 현재 사용자에게만 적용되는 레지스트리 => 사용자 권한 필요
따라서, 인스톨러의 실행 권한에 따라 사용하는 레지스트리도 맞춰 주어야 정상 동작 할 수 있다.
비정상 동작 예:
프로그램은 유저 권한으로 만들어서 사용자 권한으로 실행 했는데, 레지스트리는 HKLM을 사용하면 레지스트리 값을 제대로 못 쓰거나/읽을 수 있다.
프로그램은 어드민 권한으로 만들어서 모든 사용자 용으로 설치했는데, 레지스트리는 HKCU를 사용하면, 설치한 사용자 외의 사용자 계정으로 로그인해서 프로그램 실행 시 레지스트리 값을 정상적으로 읽어오지 못할 수 있다.
UAC가 적용되는 윈도우 버전
Windows 10, Windows 7, Windows 8, Windows Vista, Windows XP
터미널에서 아래 명령을 실행하여 resource.qrc 파일을 .py 파일로 변환합니다. (pyrcc5.exe가 설치되어 있어야 함)
pyrcc5 resource.qrc -o resource_qrc.py
3) 리소스 파일 import
리소스를 사용하고자 하는 python 파일에서 아래와 같이 리소스를 import 합니다.
from . import resource_qrc
여기까지 하면 파이썬 코드에서 리소스를 사용하기 위한 준비가 끝났습니다.
아래에서는 리소스에서 파일을 읽어 사용하는 법을 알아보도록 하겠습니다.
2. 파이썬 코드에서 Resource 의 데이터 사용하기
읽을 파일의 경로명 앞에 ":" 을 추가하고 prefix를 포함한 경로명을 적어주면 됩니다.
1) 그림 파일의 경우
위의 예에서 본 리소스 파일에서, img/icon.jpg 을 읽어오는 방법입니다.
pixmap = QPixmap(":/img/icon.jpg")
아이콘으로 읽고 싶다면 아래와 같이 합니다.
icon = QIcon(":/img/icon.jpg")
2) 텍스트 파일의 경우
- 일반적으로 python에서는 with open 또는 open 함수를 이용해 파일을 열지만, 리소스 파일 안에 있는 텍스트나 바이너리 파일을 읽는 경우 QFile 클래스를 사용해야 합니다.
- 파이썬에서 open 후 readlines() 하면 자동으로 라인별로 split된 결과가 리턴되지만, QTextStream.readAll()을 하면 통으로 된 데이터가 리턴됩니다. 따라서, 아래와 같이 '\n'으로 split한 결과를 리턴하는 함수를 만들면 readlines와 유사한 결과를 얻을 수 있습니다.
lines = readTextFileFromResource(":/data/text.txt")
def readDataFromResource(path, codec = 'UTF-8'):
fd = QFile(path)
data = None
if fd.open(QIODevice.ReadOnly | QFile.Text):
ts = QTextStream(fd)
ts.setCodec(codec)
data = ts.readAll()
fd.close()
return data.split('\n')
/* QcomboBox style */
QComboBox {
border: 1px solid gray; /* Border */
border-radius: 3px; /* Round */
padding: 1px 18px 1px 3px; /* Font fill */
color: #000;
font: normal normal 15px "Microsoft YaHei";
background: transparent;
}
/* After dropping, the entire drop-down form style */
QComboBox QAbstractItemView {
outline: 0px solid gray; /* The virtual frame of the selected item */
border: 1px solid yellow; /* Border of the entire drop-down form */
color: green;
background-color: red; /* Whole drop-down form background color */
selection-background-color: lightgreen; /* The whole drop-down window is selected from the background color of the item */
}
/* Down pull, the entire drop-down window */
QComboBox QAbstractItemView::item {
height: 50px; /* The height of the item (set pcomboBox-> setView (new qlistview ()); after this item works) */
}
/* After dropping, the entire drop-down window crossing each pattern */
QComboBox QAbstractItemView::item:hover {
color: #FFFFFF;
background-color: lightgreen; /* The whole drop-down window crosss the background color of each item */
}
/* After dropping, the entire drop-down window is selected for each pattern. */
QComboBox QAbstractItemView::item:selected {
color: #FFFFFF;
background-color: lightgreen;
}
/* Vertical scroll bar in qcomboBox */
QComboBox QAbstractScrollArea QScrollBar:vertical {
width: 10px;
background-color: #d0d2d4; /* Blank area Gray Green */
}
QComboBox QAbstractScrollArea QScrollBar::handle:vertical {
border-radius: 5px; /* Round */
background: rgb(160,160,160); /* Small square background color dark gray lightblue */
}
QComboBox QAbstractScrollArea QScrollBar::handle:vertical:hover {
background: rgb(90, 91, 93); /* Crossing the small square background color Yellow */
}
/* Set to edit (TRUE) Editable, the style of editing area */
QComboBox:editable {
background: green;
}
/* Set to non-editing (STEDITABLE)! EDITABLE, the whole qComboBox style */
QComboBox:!editable {
background: blue;
}
/* When set to edit Editable, click on the style of the entire QComboBox */
QComboBox:editable:on {
background: green;
}
/* When set to non-editing! Editable, click on the whole qComboBox style. */
QComboBox:!editable:on {
background: blue;
}
/* Set to edit Editable, the drop-down box style */
QComboBox::drop-down:editable {
background: lightblue;
}
/* When set to edit Editable, click the dropped box style. */
QComboBox::drop-down:editable:on {
background: lightgreen;
}
/* Set to non-editing! Editable, drop-down box style */
QComboBox::drop-down:!editable {
background: lightblue;
}
/* When set to non-editing! Editable, click the dropped box style. */
QComboBox::drop-down:!editable:on {
background: lightgreen;
}
/* Click qComboBox */
QComboBox:on {
}
/* Pull-up box pattern */
QComboBox::drop-down {
subcontrol-origin: padding; /* The origin rectangle of the child control in the parent element. If this property is not specified, the default is padding. */
subcontrol-position: top right; /* Down-pull box position (upper right) */
width: 15px; /* Down-pull box width */
border-left-width: 1px; /* Down-pull frame on the left boundary width */
border-left-color: darkgray; /* Down-pull frame on the left line color */
border-left-style: solid; /* The left side of the drop-down frame is a solid line */
border-top-right-radius: 3px; /* Rounded radius of the upper right border line of the drop box (should be consistent with the rounded radius of the entire QCOMBOBOX right on the right side) */
border-bottom-right-radius: 3px; /* Equally */
}/ * Crossing the pull frame pattern * /
QComboBox::drop-down:hover { background: yellow; }
/* Drop the arrow style */
QComboBox::down-arrow {
width: 15px; /* The width of the drop-down arrow (recommended with the width of the drop-down Drop-Down) */
background: transparent; /* Drop the arrow background color */
padding: 0px 0px 0px 0px; /* Upper margins, right inner margins, lower margins, left within and left */
image: url(:/images/combobox_arrow_down.png); }
/* Click to pull the arrow */
QComboBox::down-arrow:on { image: url(:/images/combobox_arrow_up.png); /* Show drop-down arrow */ }
QComboBox::indicator{
background-color:transparent;
selection-background-color:transparent;
color:transparent;
selection-color:transparent;
}
note: QComboBox* pComboBox = new QComboBox(this);
pComboBox->setView(new QListView()); //The drop-down column key can take effect
지난번 탭 위젯의 두번째 탭에, QTableWidget을 사용해 테이블을 추가해 보도록 하겠습니다.
테이블의 데이터는 csv 파일로부터 읽도록 합니다.
결과 화면 :
소스 코드 :
TabWidgetB 클래스에 아래의 내용을 추가합니다.
QTableWidget을 추가합니다.
setRowCount와 setColumnCount로 몇 행 몇 열의 테이블인지를 지정해 주어야 합니다. (데이터 파일의 총 line 수를 table의 rowCount로, 한 라인 안의 데이터 수를 table의 colCount로 설정합니다.)
setHorizontalHeaderLabels 로 테이블 헤더를 만들어 줍니다.
csv 파일에서 데이터를 읽습니다. (,로 구분된 파일)
각 row, col에 읽은 데이터를 item으로 추가합니다.
아래에서는, (0,0) 셀부터 (2,2)셀까지 각 QTableWidgetItem을 만들어 추가해 주었습니다.
필요하다면, setStyleSheet으로 헤더와 각 셀의 스타일을 지정합니다.
# qt/tabwidget/TabWidget.py
class TabWidgetB(QWidget):
def __init__(self):
QWidget.__init__(self)
self.tableWidget = QTableWidget(self)
self.tableWidget.setMinimumSize(500, 200)
self.tableWidget.setColumnCount(3)
self.tableWidget.setHorizontalHeaderLabels(["header-1", "header-2", "header-3"])
# read data from file
# 경로는 본인 파일이 있는 경로로 설정
# (main 함수가 있는 .py 파일 위치부터의 경로)
with open('venv/qt/tabwidget/data/tab.csv', 'r', encoding='utf-8') as f:
data = f.readlines()
# Set data to tableWidget
self.tableWidget.setRowCount(len(data))
rowIdx = 0
for line in data:
cells = line.split(",")
for colIdx in range(len(cells)):
item = QTableWidgetItem(cells[colIdx])
self.tableWidget.setItem(rowIdx, colIdx, item)
rowIdx += 1
self.tableWidget.setStyleSheet("QHeaderView::section {font-size:22px;}"
"QTableWidget {font-size:22px;} "
"QTableWidget::item{border-bottom:1px solid #fff; border-right:1px solid #fff;}")
self.tableWidget.resizeColumnsToContents()
self.tableWidget.resizeRowsToContents()
vBox = QVBoxLayout(self)
vBox.addWidget(self.tableWidget)
self.setLayout(vBox)
아래는 탭 위젯에 사진 탭, 테이블 탭을 추가한 전체 소스코드 입니다.
1. qt/tabwidget/TabWidget.py
# qt/tabwidget/TabWidget.py
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QTabWidget, \
QVBoxLayout, QScrollArea, QDialog, QTableWidget, QTableWidgetItem
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
class TabDialog(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle("Tab Dialog")
# Tab widget
self.tabWidget = QTabWidget(self)
self.tabWidget.addTab(TabWidgetA(), "tab1")
self.tabWidget.addTab(TabWidgetB(), "tab2")
self.tabWidget.setStyleSheet("QTabBar {font-size:30px;}")
self.label = QLabel(self)
self.label.setText("Title")
self.label.setStyleSheet("font-size:40px; font-weight: bold;")
self.label.setAlignment(Qt.AlignCenter)
self.mainLayout = QVBoxLayout(self)
self.mainLayout.addWidget(self.label, Qt.AlignHCenter)
self.mainLayout.addWidget(self.tabWidget)
self.setLayout(self.mainLayout)
self.resize(600,400)
class TabWidgetA(QWidget):
def __init__(self):
QWidget.__init__(self)
mainLayout = QVBoxLayout(self)
self.label = QLabel(self)
self.label.setText("Label text")
self.scroll = QScrollArea(self)
widget = QWidget()
vBox = QVBoxLayout()
for name in ('0001', '0002', '0003'):
pixmap = QPixmap("venv/qt/tabwidget/data/{}.jpg".format(name))
label = QLabel(self)
label.setPixmap(pixmap)
label.resize(pixmap.width(), pixmap.height())
vBox.addWidget(label)
widget.setLayout(vBox)
self.scroll.setWidget(widget)
self.scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn)
self.scroll.setWidgetResizable(True)
mainLayout.addWidget(self.label)
mainLayout.addWidget(self.scroll)
self.setLayout(mainLayout)
class TabWidgetB(QWidget):
def __init__(self):
QWidget.__init__(self)
self.tableWidget = QTableWidget(self)
self.tableWidget.setMinimumSize(500, 200)
self.tableWidget.setColumnCount(3)
self.tableWidget.setHorizontalHeaderLabels(["header-1", "header-2", "header-3"])
# read data from file
with open('venv/qt/tabwidget/data/tab.csv', 'r', encoding='utf-8') as f:
data = f.readlines()
# Set data to tableWidget
self.tableWidget.setRowCount(len(data))
rowIdx = 0
for line in data:
cells = line.split(",")
for colIdx in range(len(cells)):
item = QTableWidgetItem(cells[colIdx])
self.tableWidget.setItem(rowIdx, colIdx, item)
rowIdx += 1
self.tableWidget.setStyleSheet("QHeaderView::section {font-size:22px;}"
"QTableWidget {font-size:22px;} "
"QTableWidget::item{border-bottom:1px solid #fff; border-right:1px solid #fff;}")
self.tableWidget.resizeColumnsToContents()
self.tableWidget.resizeRowsToContents()
vBox = QVBoxLayout(self)
vBox.addWidget(self.tableWidget)
self.setLayout(vBox)
2. main.py
# main.py
from qt.tabwidget.TabWidget import * # 본인의 소스파일이 있는 경로를 적고 import해주세요
import sys
def start():
app = QApplication(sys.argv)
tabDialog = TabDialog()
tabDialog.show()
sys.exit(app.exec_())
if __name__ == '__main__':
start()
1. QDialog를 상속받은 다이얼로그 클래스를 만들고, QTabWidget에 addTab()을 해서 각 탭을 추가합니다.
# TabWidget.py
from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QTabWidget, \
QVBoxLayout, QScrollArea, QDialog, QTableWidget, QTableWidgetItem
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap
class TabDialog(QDialog):
def __init__(self):
QDialog.__init__(self)
self.setWindowTitle("Tab Dialog")
# Tab widget
self.tabWidget = QTabWidget(self)
self.tabWidget.addTab(TabWidgetA(), "tab1") # 탭1 추가 - 여기 적힌 내용이 탭 제목으로 보여짐
self.tabWidget.addTab(TabWidgetB(), "tab2") # 탭2 추가 - 여기 적힌 내용이 탭 제목으로 보여짐
self.mainLayout = QVBoxLayout(self)
self.mainLayout.addWidget(self.tabWidget)
self.setLayout(self.mainLayout)
self.resize(600,400)
2. 추가되는 각 탭에 들어가는 내용은 QWidget을 상속받은 클래스로 만들어 줍니다.
# TabWidget.py
class TabWidgetA(QWidget):
def __init__(self):
QWidget.__init__(self)
# Do Something here...
class TabWidgetB(QWidget):
def __init__(self):
QWidget.__init__(self)
# Do Something here...