ZetCode-GUI-教程-一-

龙哥盟 / 2024-11-20 / 原文

ZetCode GUI 教程(一)

原文:ZetCode

协议:CC BY-NC-SA 4.0

Windows API 菜单

原文: http://zetcode.com/gui/winapi/menus/

在 Windows API 教程的这一部分中,我们创建菜单。菜单是位于菜单栏中的一组命令。菜单栏包含菜单列表。 菜单可以包含菜单项或其他菜单调用子菜单。 执行命令的菜单项称为命令项或命令。 在 Windows 上,菜单栏有时称为顶层菜单。 菜单和子菜单称为弹出菜单。 菜单项通常分为一些逻辑组。 这些组由分隔符分隔。 分隔符是一条小的水平线。

一个简单的菜单

在下面的示例中,我们创建一个菜单栏和三个菜单命令。 我们还创建一个分隔符。

simplemenu.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void AddMenus(HWND);

#define IDM_FILE_NEW 1
#define IDM_FILE_OPEN 2
#define IDM_FILE_QUIT 3

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Simple menu";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Simple menu",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 350, 250, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

  switch(msg) {

      case WM_CREATE:

          AddMenus(hwnd);
          break;

      case WM_COMMAND:

          switch(LOWORD(wParam)) {

              case IDM_FILE_NEW:
              case IDM_FILE_OPEN:

                  MessageBeep(MB_ICONINFORMATION);
                  break;

              case IDM_FILE_QUIT:

                  SendMessage(hwnd, WM_CLOSE, 0, 0);
                  break;
           }

           break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break;
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void AddMenus(HWND hwnd) {

    HMENU hMenubar;
    HMENU hMenu;

    hMenubar = CreateMenu();
    hMenu = CreateMenu();

    AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New");
    AppendMenuW(hMenu, MF_STRING, IDM_FILE_OPEN, L"&Open");
    AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
    AppendMenuW(hMenu, MF_STRING, IDM_FILE_QUIT, L"&Quit");

    AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&File");
    SetMenu(hwnd, hMenubar);
}

两个菜单项发出短促的声音。 第三个终止应用。

case WM_COMMAND:

    switch(LOWORD(wParam)) {

        case IDM_FILE_NEW:
        case IDM_FILE_OPEN:

            MessageBeep(MB_ICONINFORMATION);
            break;

        case IDM_FILE_QUIT:

            SendMessage(hwnd, WM_CLOSE, 0, 0);
            break;
    }

    break;

如果选择菜单项,则窗口过程会收到WM_COMMAND消息。 菜单项 id 在wParam值的低位字中。

hMenubar = CreateMenu();
hMenu = CreateMenu();

使用CreateMenu()函数创建菜单栏和菜单。

AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New");
AppendMenuW(hMenu, MF_STRING, IDM_FILE_OPEN, L"&Open");
AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
AppendMenuW(hMenu, MF_STRING, IDM_FILE_QUIT, L"&Quit");

AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&File");

菜单项和子菜单是使用AppendMenuW()函数创建的。 我们要附加的内容取决于标志。 MF_STRING附加标签,MF_SEPARATOR附加分隔符,MF_POPUP附加菜单。

SetMenu(hwnd, hMenubar);

最后,我们设置菜单栏,调用SetMenu()函数。

A menu example

图:简单菜单

弹出菜单

弹出菜单也称为上下文菜单。 它是在某些情况下显示的命令列表。 例如,在 Firefox Web 浏览器中,当我们右键单击网页时,将获得一个上下文菜单。 在这里,我们可以重新加载页面,返回页面或查看页面源。 如果右键单击工具栏,则将获得另一个用于管理工具栏的上下文菜单。

popupmenu.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

#define IDM_FILE_NEW 1
#define IDM_FILE_OPEN 2
#define IDM_FILE_QUIT 3

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {
    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Popup menu";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Popup menu",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 350, 250, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg,
    WPARAM wParam, LPARAM lParam) {

    HMENU hMenu;
    POINT point;

    switch(msg) {

      case WM_COMMAND:

          switch(LOWORD(wParam)) {

              case IDM_FILE_NEW:
              case IDM_FILE_OPEN:

                  MessageBeep(MB_ICONINFORMATION);
                  break;

              case IDM_FILE_QUIT:

                  SendMessage(hwnd, WM_CLOSE, 0, 0);
                  break;
          }

          break;

      case WM_RBUTTONUP:

          point.x = LOWORD(lParam);
          point.y = HIWORD(lParam);

          hMenu = CreatePopupMenu();
          ClientToScreen(hwnd, &point);

          AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New");
          AppendMenuW(hMenu, MF_STRING, IDM_FILE_OPEN, L"&Open");
          AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
          AppendMenuW(hMenu, MF_STRING, IDM_FILE_QUIT, L"&Quit");

          TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL);
          DestroyMenu(hMenu);
          break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

我们有一个上下文菜单示例,其中包含三个菜单项。

case WM_RBUTTONUP:

    point.x = LOWORD(lParam);
    point.y = HIWORD(lParam);
...    

当光标在窗口的客户区域中时,用户释放鼠标右键时,将发布WM_RBUTTONUP消息。 lParam的低位字指定光标的 x 坐标。 高阶字指定光标的 y 坐标。 坐标相对于客户区域的左上角。

hMenu = CreatePopupMenu();   

CreatePopupMenu()函数创建一个弹出菜单。 它将句柄返回到新创建的菜单。 菜单最初是空的。

ClientToScreen(hwnd, &point);   

ClientToScreen()函数将指定点的客户坐标转换为屏幕坐标。 我们需要这些坐标才能显示上下文菜单。

AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New");
AppendMenuW(hMenu, MF_STRING, IDM_FILE_OPEN, L"&Open");
AppendMenuW(hMenu, MF_SEPARATOR, 0, NULL);
AppendMenuW(hMenu, MF_STRING, IDM_FILE_QUIT, L"&Quit");   

创建三个菜单项和一个分隔符。

TrackPopupMenu(hMenu, TPM_RIGHTBUTTON, point.x, point.y, 0, hwnd, NULL);   

TrackPopupMenu()函数在指定位置显示上下文菜单,并跟踪菜单上项目的选择。

DestroyMenu(hMenu);   

最后,使用DestroyMenu()函数销毁菜单对象。 未分配给窗口的菜单必须被明确销毁。

A popup menu

图:弹出菜单

复选菜单项

复选菜单项是在标签前带有复选标记的菜单项。 菜单项可以使用CheckMenuItem()函数选中或取消选中。

checkmenuitem.c

#include <windows.h>
#include <commctrl.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void AddMenus(HWND);

#define IDM_VIEW_STB 1

HWND ghSb;
HMENU ghMenu;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Check menu item";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Check menu item",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 350, 250, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    UINT state;

    switch(msg) {

      case WM_CREATE:

          AddMenus(hwnd);          
          InitCommonControls();

          ghSb = CreateWindowExW(0, STATUSCLASSNAMEW, NULL, 
              WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, 
              (HMENU) 1, GetModuleHandle(NULL), NULL);

          break;

      case WM_COMMAND:

          switch(LOWORD(wParam)) {

              case IDM_VIEW_STB:                                    

                  state = GetMenuState(ghMenu, IDM_VIEW_STB, MF_BYCOMMAND); 

                  if (state == MF_CHECKED) {

                      ShowWindow(ghSb, SW_HIDE);
                      CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_UNCHECKED);  
                  } else {

                      ShowWindow(ghSb, SW_SHOWNA);
                      CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_CHECKED);  
                  }

                  break;
          }

          break;

      case WM_SIZE:

          SendMessage(ghSb, WM_SIZE, wParam, lParam);          
          break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void AddMenus(HWND hwnd) {

    HMENU hMenubar;

    hMenubar = CreateMenu();
    ghMenu = CreateMenu();

    AppendMenuW(ghMenu, MF_STRING, IDM_VIEW_STB, L"&Statusbar");
    CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_CHECKED);  

    AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) ghMenu, L"&View");

    SetMenu(hwnd, hMenubar);
}

在示例中,我们有一个包含一个菜单项的视图菜单。 该菜单项将显示或隐藏状态栏。 当状态栏可见时,将选中菜单项。

#define IDM_VIEW_STB 1

这是将显示或隐藏状态栏的菜单项的 ID。

InitCommonControls();

状态栏是常用控件。 必须使用InitCommonControls()函数对其进行初始化。

ghSb = CreateWindowExW(0, STATUSCLASSNAMEW, NULL, 
    WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, 
    (HMENU) 1, GetModuleHandle(NULL), NULL);  

此代码行创建一个状态栏控件。

state = GetMenuState(ghMenu, IDM_VIEW_STB, MF_BYCOMMAND); 

我们通过GetMenuState()函数获得状态栏菜单项的状态。

if (state == MF_CHECKED) {

    ShowWindow(ghSb, SW_HIDE);
    CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_UNCHECKED);  
} else {

    ShowWindow(ghSb, SW_SHOWNA);
    CheckMenuItem(ghMenu, IDM_VIEW_STB, MF_CHECKED);  
}

根据其状态,我们使用ShowWindow()函数显示或隐藏状态栏控件。 菜单项通过CheckMenuItem()函数被选中或取消选中。

case WM_SIZE:

    SendMessage(ghSb, WM_SIZE, wParam, lParam);          
    break;

调整窗口大小后,我们将调整状态栏的大小以适合窗口。

A check menu item

图:复选菜单项

单选菜单项

单选菜单项使您可以从互斥的选项列表中进行选择。 单选菜单项通过CheckMenuRadioItem()函数进行管理。

radiomenuitem.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void AddMenus(HWND);

#define IDM_MODE_MAP 1
#define IDM_MODE_SAT 2
#define IDM_MODE_TRA 3
#define IDM_MODE_STR 4

HMENU hMenu;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Radio menu item";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Radio menu item",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 350, 250, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

  switch(msg) {

      case WM_CREATE:

          AddMenus(hwnd);
          break;

      case WM_COMMAND:

          switch(LOWORD(wParam)) {

              case IDM_MODE_MAP:
                  CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
                      IDM_MODE_MAP, MF_BYCOMMAND);
                  MessageBeep(MB_ICONERROR);
                  break;

              case IDM_MODE_SAT:
                  CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
                      IDM_MODE_SAT, MF_BYCOMMAND);
                  MessageBeep(0xFFFFFFFF);
                  break;

              case IDM_MODE_TRA:
                  CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
                      IDM_MODE_TRA, MF_BYCOMMAND);
                  MessageBeep(MB_ICONWARNING);
                  break;

              case IDM_MODE_STR:
                  CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
                      IDM_MODE_STR, MF_BYCOMMAND);

                  MessageBeep(MB_ICONINFORMATION);
                  break;
           }

           break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break;
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void AddMenus(HWND hwnd) {

    HMENU hMenubar;

    hMenubar = CreateMenu();
    hMenu = CreateMenu();

    AppendMenuW(hMenu, MF_STRING, IDM_MODE_MAP, L"&Map");
    AppendMenuW(hMenu, MF_STRING, IDM_MODE_SAT, L"&Satellite");
    AppendMenuW(hMenu, MF_STRING, IDM_MODE_TRA, L"&Traffic");
    AppendMenuW(hMenu, MF_STRING, IDM_MODE_STR, L"Street &view");

    CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
        IDM_MODE_MAP, MF_BYCOMMAND);

    AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&Map mode");
    SetMenu(hwnd, hMenubar);
}

在示例中,我们有四个单选菜单项; 一次只能选择其中之一。 每个单选菜单项都会发出不同的声音。

#define IDM_MODE_MAP 1
#define IDM_MODE_SAT 2
#define IDM_MODE_TRA 3
#define IDM_MODE_STR 4

这些是单选菜单项的 ID。

case IDM_MODE_MAP:
    CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
        IDM_MODE_MAP, MF_BYCOMMAND);
    MessageBeep(MB_ICONERROR);
    break;

CheckMenuRadioItem()检查指定的菜单项并将其设为单选项目。 此外,它清除关联的菜单项组中的所有其他菜单项。 该函数的第一个参数是包含菜单项组的菜单句柄。 最后一个参数指示前三个参数的含义; 当指定MF_BYCOMMAND时,这些参数是菜单项的 ID。 第二个参数是组中第一个菜单项的 ID,第三个参数是组中最后一个菜单项的 ID。 第四个参数是要检查的菜单标识符。

...
AppendMenuW(hMenu, MF_STRING, IDM_MODE_STR, L"Street &view");

CheckMenuRadioItem(hMenu, IDM_MODE_MAP, IDM_MODE_STR, 
    IDM_MODE_MAP, MF_BYCOMMAND);

AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&Map mode");
...

首先,选择第一个单选项目。

A radio menu item

图:单选菜单项

子菜单

子菜单是位于另一个菜单内的菜单。

submenu.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void AddMenus(HWND);

#define IDM_FILE_NEW 1
#define IDM_FILE_IMPORT 2

#define IDM_IMPORT_MAIL 11

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Submenu";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Submenu",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 350, 250, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

  switch(msg) {

      case WM_CREATE:

          AddMenus(hwnd);
          break;

      case WM_COMMAND:

          switch(LOWORD(wParam)) {

              case IDM_FILE_NEW:
                  MessageBoxW(NULL, L"New file selected", 
                        L"Information", MB_OK);
                  break;

              case IDM_IMPORT_MAIL:
                  MessageBoxW(NULL, L"Import mail selected", 
                        L"Information", MB_OK);
           }

           break;

      case WM_DESTROY:

          PostQuitMessage(0);
          break;
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void AddMenus(HWND hwnd) {

    HMENU hMenubar = CreateMenu();
    HMENU hMenu = CreateMenu();
    HMENU hSubMenu = CreatePopupMenu();

    AppendMenuW(hMenu, MF_STRING, IDM_FILE_NEW, L"&New");

    AppendMenuW(hMenu, MF_STRING | MF_POPUP, (UINT_PTR) hSubMenu, L"&Import");
    AppendMenuW(hSubMenu, MF_STRING, IDM_IMPORT_MAIL, L"Import &mail");

    AppendMenuW(hMenubar, MF_POPUP, (UINT_PTR) hMenu, L"&File");
    SetMenu(hwnd, hMenubar);
}

在示例中,我们有两个菜单项; 一个位于“文件”菜单中,另一个位于“文件的导入”子菜单中。 选择每个菜单项都会显示一个消息框。

case IDM_IMPORT_MAIL:
    MessageBoxW(NULL, L"Import mail selected", 
          L"Information", MB_OK);

当我们选择“导入邮件”子菜单项时,将显示一个带有"Import mail selected"文本的消息框。

HMENU hSubMenu = CreatePopupMenu();

使用CreatePopupMenu()函数创建一个子菜单。

AppendMenuW(hMenu, MF_STRING | MF_POPUP, (UINT_PTR) hSubMenu, L"&Import");

通过AppendMenuW()函数,我们在文件菜单中添加了一个子菜单。 MF_POPUP标志用于弹出菜单和子菜单。

AppendMenuW(hSubMenu, MF_STRING, IDM_IMPORT_MAIL, L"Import &mail");

通常使用AppendMenuW()函数将菜单项添加到子菜单。

Submenu

图:子菜单

在 Windows API 教程的这一部分中,我们介绍了菜单。

wxPython 简介

http://zetcode.com/wxpython/introduction/

本章是 wxPython 工具箱的简介。

wxPython 是用于创建桌面 GUI 应用的跨平台工具包。 wxPython 的主要作者是 Robin Dunn。 使用 wxPython,开发者可以在 Windows,Mac 和各种 Unix 系统上创建应用。 wxPython 是 wxWidgets 的包装,wxWidgets 是成熟的跨平台 C++ 库。

Python

Python logo Python 是一种成功的脚本语言。 它最初由 Guido van Rossum 开发。 它于 1991 年首次发布。Python 受 ABC 和 Haskell 编程语言的启发。 Python 是高级通用多平台解释型语言。 有些人喜欢将其称为动态语言。 这很容易学习。 Python 是一种简约语言。 它最明显的功能之一是它不使用分号或方括号。 Python 使用缩进代替。 今天,Python 由世界各地的一大批志愿者维护。

为了创建图形用户界面,Python 程序员可以在三个合适的选项中进行选择:PyGTK,wxPython 和 PyQt。

wxPython 模块

wxPython 是用于创建桌面 GUI 应用的跨平台工具包。 wxPython 的主要作者是 Robin Dunn。 使用 wxPython,开发者可以在 Windows,Mac 和各种 Unix 系统上创建应用。 wxPython 是 wxWidgets 的包装,wxWidgets 是成熟的跨平台 C++ 库。 wxPython 由五个基本模块组成。

wxPython modules

图:wxPython 模块

控件模块提供了图形应用中常见的窗口小部件。 例如,按钮,工具栏或笔记本。 小部件在 Windows OS 下称为控件。核心模块由开发中使用的基本类组成。 这些类包括Object类(它是所有类的母类),大小调整器(用于小部件布局),事件,基本几何类(如PointRectangle)。 图形设备接口(GDI)是用于绘制到小部件上的一组类。 此模块包含用于处理字体,颜色,画笔,笔或图像的类。其他模块包含各种其他类和模块功能。 这些类用于日志记录,应用配置,系统设置,与显示器或操纵杆一起使用。Windows 模块由构成应用的各种窗口组成,例如面板,对话框,框架或滚动窗口。

wxPython API

wxPython API 是一组方法和对象。 小部件是 GUI 应用的基本构建块。 在 Windows 小部件下是被调用控件。 我们可以将程序员大致分为两类:他们编写应用或库。 在我们的案例中,wxPython 是一个库,应用员使用该库对应用进行编码。 从技术上讲,wxPython 是名为 wxWidgets 的 C++ GUI API 的包装。 因此它不是本机 API。 即它不是直接用 Python 编写的。

在 wxPython 中,我们有很多小部件。 这些可以分为一些逻辑组。

基本小部件

这些小部件为派生的小部件提供基本功能。 他们被称为祖先。 它们通常不直接使用。

Base widgets

图:Base小部件

顶级小部件

这些小部件彼此独立存在。

Top-level widgets

图:顶级小部件

容器

容器包含其他小部件。

Containters

图:容器

动态小部件

这些窗口小部件可以由用户编辑。

Dynamic widgets

图:Dynamic小部件

静态小部件

这些小部件显示信息。 它们不能由用户编辑。

Static widgets

图:Static小部件

其他小工具

这些小部件在应用中实现状态栏,工具栏和菜单栏。

Other widgets

图:Other小部件

继承

wxPython 中的小部件之间有特定的关系。 此关系是通过继承开发的。 继承是面向对象编程的关键部分。 小部件形成层次结构。 小部件可以继承其他小部件的功能。 现有的类称为基类,父级或祖先。 继承的小部件我们称为派生小部件,子小部件或后代。

Inheritance diagram

图:继承图

假设我们在应用中使用按钮小部件。 按钮小部件继承自四个不同的基类。 最接近的类是wx.Control类。 按钮小部件是一种小窗口。 屏幕上显示的所有小部件都是窗口。 因此,它们继承自wx.Window类。 有些物体是不可见的。 例如,大小调整程序,设备上下文或区域设置对象。 也有一些可见的类,但它们不是 Windows。 例如,颜色对象,插入符号对象或光标对象。 并非所有的小部件都是控件。 例如wx.Dialog不是一种控件。 控件是放置在称为容器的其他窗口小部件上的窗口小部件。 这就是为什么我们有一个单独的wx.Control基类。

每个窗口都可以对事件做出反应。 按钮小部件也是如此。 通过单击按钮,我们启动wx.EVT_COMMAND_BUTTON_CLICKED事件。 按钮小部件通过wx.Window类继承wx.EvtHandler。 每个响应事件的小部件都必须继承wx.EvtHandler类。 最后,所有对象都继承自wx.Object类。

这是 wxPython 的简介。

第一步

原文: http://zetcode.com/wxpython/firststeps/

在 wxPython 教程的这一部分中,我们将创建一些简单的示例。

简单的例子

我们从一个非常简单的例子开始。 我们的第一个脚本只会显示一个小窗口。

它不会做太多。 我们将逐行分析脚本。

simple.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# simple.py

import wx

app = wx.App()

frame = wx.Frame(None, title='Simple application')
frame.Show()

app.MainLoop()

这是我们在 wxPython 中的第一个示例。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# simple.py

第一行是 she-bang,后面是通往 Python 解释器的路径。 第二行是一个魔术注释,它指定了源代码的编码。 第四行是提供脚本名称的注释。

import wx

此行导入基本的 wxPython 模块。 即核心,控件,GDI,杂项和 windows。 从技术上讲,wx是一个名称空间。 基本模块中的所有功能和对象都将以wx.前缀开头。 下一行代码将创建一个应用对象。

app = wx.App()

每个 wxPython 程序必须具有一个应用对象。

frame = wx.Frame(None, title='Simple application')
frame.Show()

在这里,我们创建一个wx.Frame对象。 wx.Frame小部件是重要的容器小部件。 稍后我们将详细分析此小部件。 wx.Frame小部件是其他小部件的父级小部件。 它本身没有父级。 如果为父参数指定None,则表明我们的小部件没有父项。 它是小部件层次结构中的顶部小部件。 创建wx.Frame小部件后,必须调用Show()方法才能将其实际显示在屏幕上。

app.MainLoop()

最后一行进入主循环。 主循环是一个无休止的循环。 它捕获并调度应用生命周期内存在的所有事件。

这是一个非常简单的例子。 尽管如此简单,我们仍然可以在此窗口中做很多事情。 我们可以调整窗口大小,最大化,最小化。 此功能需要大量编码。 wxPython 工具箱默认隐藏了所有这些内容并提供了这些内容。 没有理由重新发明轮子。

Simple example

图:简单 example

框架

wx.Frame小部件是 wxPython 中最重要的小部件之一。 这是一个容器小部件。 这意味着它可以包含其他小部件。 实际上,它可以包含不是框架或对话框的任何窗口。 wx.Frame由标题栏,边框和中央容器区域组成。 标题栏和边框是可选的。 它们可以通过各种标志删除。

wx.Frame具有以下构造器:

wx.Frame(wx.Window parent, int id=-1, string title='', wx.Point pos=wx.DefaultPosition, 
    wx.Size size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE, string name="frame")

构造器有七个参数。 第一个参数没有默认值。 其他六个参数确实具有。 这四个参数是可选的。 前三个是强制性的。

wx.DEFAULT_FRAME_STYLE是一组默认标志:wx.MINIMIZE_BOX | wx.MAXIMIZE_BOX | wx.RESIZE_BORDER wx.SYSTEM_MENU | wx.CAPTION | wx.CLOSE_BOX | wx.CLIP_CHILDREN。 通过组合各种样式,我们可以更改wx.Frame小部件的样式。

no_minimize.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# no_minimize.py

import wx

app = wx.App()
frame = wx.Frame(None, style=wx.MAXIMIZE_BOX | wx.RESIZE_BORDER
	| wx.SYSTEM_MENU | wx.CAPTION |	 wx.CLOSE_BOX)
frame.Show(True)

app.MainLoop()

我们的意图是显示一个没有最小化框的窗口。 因此,我们没有在style参数中指定此标志。

大小和位置

我们可以通过两种方式指定应用的大小。 我们的小部件的构造器中有一个size参数,或者我们可以调用SetSize()方法。

set_size.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# set_size.py

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title,
            size=(350, 250))

def main():

    app = wx.App()
    ex = Example(None, title='Sizing')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,应用的大小为250x200像素。

def __init__(self, parent, title):
    super(Example, self).__init__(parent, title=title, 
        size=(350, 250))

在构造器中,我们将wx.Frame小部件的宽度设置为 350px。 小部件的高度为 250 像素。

同样,我们可以将应用放置在屏幕上。 默认情况下,窗口位于屏幕的左上角。 但是在各种 OS 平台甚至窗口管理器上,它可能都不同。 一些窗口管理器自己放置应用窗口。 他们中的一些人做了一些优化,以使窗口不会重叠。 程序员可以以编程方式定位窗口。 我们已经在wx.Frame小部件的构造器中看到了pos参数。 通过提供默认值以外的其他值,我们可以自己控制位置。

方法 描述
Move(wx.Point point) 将窗口移至指定位置
MoveXY(int x, int y) 将窗口移至指定位置
SetPosition(wx.Point point) 设置窗口的位置
SetDimensions(x, y, width, height, sizeFlags) 设置窗口的位置和大小

有几种方法可以做到这一点。

moving.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# moving.py

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title,
            size=(300, 200))

        self.Move((800, 250))

def  main():

    app = wx.App()
    ex = Example(None, title='Moving')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

有一种特殊情况。 我们可能要最大化显示我们的窗口。 在这种情况下,窗口位于(0,0),并占据整个屏幕。 wxPython 在内部计算屏幕坐标。 为了最大化我们的wx.Frame,我们调用Maximize()方法。

在屏幕上居中

如果要在屏幕上居中放置应用,则 wxPython 有一个方便的方法。 Centre()方法只是将窗口居中放在屏幕上。 无需计算屏幕的宽度和高度。 只需调用该方法。

centering.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# centering.py

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title,
            size=(300, 200))

        self.Centre()

def main():

    app = wx.App()
    ex = Example(None, title='Centering')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,我们在屏幕上居中了一个小窗口。

self.Centre()

Centre()方法使窗口在屏幕上居中。

在本章中,我们在 wxPython 中创建了一些简单的代码示例。

菜单和工具栏

原文: http://zetcode.com/wxpython/menustoolbars/

GUI 应用中的常见部分是菜单栏。 菜单栏由称为菜单的对象组成。 顶层菜单在菜单栏上带有其标签。 菜单具有菜单项。 菜单项是在应用内部执行特定操作的命令。 菜单也可以具有子菜单,这些子菜单具有自己的菜单项。 以下三个类用于在 wxPython 中创建菜单栏:wx.MenuBarwx.Menuwx.MenuItem

简单菜单

在第一个示例中,我们将创建一个带有一个文件菜单的菜单栏。 该菜单将只有一个菜单项。 通过选择项目,应用退出。

simple_menu.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example shows a simple menu.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        fileItem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application')
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.Bind(wx.EVT_MENU, self.OnQuit, fileItem)

        self.SetSize((300, 200))
        self.SetTitle('Simple menu')
        self.Centre()

    def OnQuit(self, e):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

这是一个最小的菜单栏功能示例。

menubar = wx.MenuBar()

首先,我们创建一个菜单栏对象。

fileMenu = wx.Menu()

接下来,我们创建一个菜单对象。

fileItem = fileMenu.Append(wx.ID_EXIT, 'Quit', 'Quit application')

我们将菜单项添加到菜单对象中。 第一个参数是菜单项的 ID。 标准 ID 将自动添加图标和快捷方式,在本例中为 Ctrl + Q 。 第二个参数是菜单项的名称。 最后一个参数定义了选择菜单项时在状态栏上显示的简短帮助字符串。 在这里,我们没有明确创建一个wx.MenuItem。 它是通过Append()方法在后台创建的。 该方法返回创建的菜单项。 稍后将使用此引用来绑定事件。

self.Bind(wx.EVT_MENU, self.OnQuit, fileItem)

我们将菜单项的wx.EVT_MENU绑定到自定义OnQuit()方法。 此方法将关闭应用。

menubar.Append(fileMenu, '&File')
self.SetMenuBar(menubar)

之后,我们将菜单添加到菜单栏中。 &字符创建一个加速键。 带下划线的&后面的字符。 这样,可以通过 Alt + F 快捷方式访问菜单。 最后,我们调用SetMenuBar()方法。 该方法属于wx.Frame小部件。 它设置菜单栏。

A simple menu example

图:简单菜单 example

图标和快捷方式

下一个示例与上一个示例基本相同。 这次,我们手动创建一个wx.MenuItem

icons_shortcuts.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we manually create
a menu item.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

APP_EXIT = 1

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        qmi = wx.MenuItem(fileMenu, APP_EXIT, '&Quit\tCtrl+Q')
        qmi.SetBitmap(wx.Bitmap('exit.png'))
        fileMenu.Append(qmi)

        self.Bind(wx.EVT_MENU, self.OnQuit, id=APP_EXIT)

        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.SetSize((350, 250))
        self.SetTitle('Icons and shortcuts')
        self.Centre()

    def OnQuit(self, e):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,我们创建一个退出菜单项。 我们为菜单项选择一个自定义图标和快捷方式。

qmi = wx.MenuItem(fileMenu, APP_EXIT, '&Quit\tCtrl+Q')
qmi.SetBitmap(wx.Bitmap('exit.png'))
fileMenu.Append(qmi)

我们创建一个wx.MenuItem对象。 &字符指定加速键。 带&号后面的字符带有下划线。 实际的快捷方式由字符组合定义。 我们指定了 Ctrl + Q 字符。 因此,如果我们按 Ctrl + Q ,我们将关闭应用。 我们在&字符和快捷方式之间放置一个制表符。 这样,我们设法在它们之间留出一些空间。 为了提供菜单项的图标,我们调用SetBitmap()方法。 通过调用AppendItem()方法将手动创建的菜单项附加到菜单。

self.Bind(wx.EVT_MENU, self.OnQuit, id=APP_EXIT)

当我们选择创建的菜单项时,将调用OnQuit()方法。

Icons and shortcuts

图:图标 s and shortcuts

子菜单和分隔符

每个菜单也可以有一个子菜单。 这样,我们可以将类似的命令分组。 例如,我们可以将隐藏/显示各种工具栏(例如个人栏,地址栏,状态栏或导航栏)的命令放置在称为工具栏的子菜单中。 在菜单中,我们可以使用分隔符来分隔命令。 这是一条简单的线。 通常的做法是使用单个分隔符将“新建”,“打开”,“保存”等命令与“打印”,“打印预览”等命令分开。 在我们的示例中,我们将看到如何创建子菜单和菜单分隔符。

submenu.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a submenu and a menu
separator.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        menubar = wx.MenuBar()

        fileMenu = wx.Menu()
        fileMenu.Append(wx.ID_NEW, '&New')
        fileMenu.Append(wx.ID_OPEN, '&Open')
        fileMenu.Append(wx.ID_SAVE, '&Save')
        fileMenu.AppendSeparator()

        imp = wx.Menu()
        imp.Append(wx.ID_ANY, 'Import newsfeed list...')
        imp.Append(wx.ID_ANY, 'Import bookmarks...')
        imp.Append(wx.ID_ANY, 'Import mail...')

        fileMenu.AppendMenu(wx.ID_ANY, 'I&mport', imp)

        qmi = wx.MenuItem(fileMenu, wx.ID_EXIT, '&Quit\tCtrl+W')
        fileMenu.AppendItem(qmi)

        self.Bind(wx.EVT_MENU, self.OnQuit, qmi)

        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        self.SetSize((350, 250))
        self.SetTitle('Submenu')
        self.Centre()

    def OnQuit(self, e):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的示例中,我们创建了“新建”,“打开”和“保存”标准菜单项。 这些与带有水平分隔符的子菜单分开。 子菜单具有其他三个菜单项。

fileMenu.Append(wx.ID_NEW, '&New')
fileMenu.Append(wx.ID_OPEN, '&Open')
fileMenu.Append(wx.ID_SAVE, '&Save')

在这里,我们有三个常见的菜单项:新建,打开和保存。

fileMenu.AppendSeparator()

菜单分隔符随AppendSeparator()方法附加。

imp = wx.Menu()
imp.Append(wx.ID_ANY, 'Import newsfeed list...')
imp.Append(wx.ID_ANY, 'Import bookmarks...')
imp.Append(wx.ID_ANY, 'Import mail...')

fileMenu.AppendMenu(wx.ID_ANY, 'I&mport', imp)

子菜单也是wx.Menu。 三个菜单项附加到菜单。 子菜单通过AppenMenu()方法附加到文件菜单。

A submenu example

图:一个子菜单示例

复选菜单项

有树形菜单项。

  • 普通项目
  • 复选项目
  • 单选项目

在下面的示例中,我们将演示复选菜单项。 复选菜单项在菜单中由刻度线直观地表示。

checkmenu_item.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example creates a checked
menu item.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        menubar = wx.MenuBar()
        viewMenu = wx.Menu()

        self.shst = viewMenu.Append(wx.ID_ANY, 'Show statusbar',
            'Show Statusbar', kind=wx.ITEM_CHECK)
        self.shtl = viewMenu.Append(wx.ID_ANY, 'Show toolbar',
            'Show Toolbar', kind=wx.ITEM_CHECK)

        viewMenu.Check(self.shst.GetId(), True)
        viewMenu.Check(self.shtl.GetId(), True)

        self.Bind(wx.EVT_MENU, self.ToggleStatusBar, self.shst)
        self.Bind(wx.EVT_MENU, self.ToggleToolBar, self.shtl)

        menubar.Append(viewMenu, '&View')
        self.SetMenuBar(menubar)

        self.toolbar = self.CreateToolBar()
        self.toolbar.AddTool(1, '', wx.Bitmap('texit.png'))
        self.toolbar.Realize()

        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetStatusText('Ready')

        self.SetSize((450, 350))
        self.SetTitle('Check menu item')
        self.Centre()

    def ToggleStatusBar(self, e):

        if self.shst.IsChecked():
            self.statusbar.Show()
        else:
            self.statusbar.Hide()

    def ToggleToolBar(self, e):

        if self.shtl.IsChecked():
            self.toolbar.Show()
        else:
            self.toolbar.Hide()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们有一个视图菜单,其中有两个复选菜单项。 这两个菜单项将显示和隐藏状态栏和工具栏。

self.shst = viewMenu.Append(wx.ID_ANY, 'Show statusbar', 
    'Show Statusbar', kind=wx.ITEM_CHECK)
self.shtl = viewMenu.Append(wx.ID_ANY, 'Show toolbar', 
    'Show Toolbar', kind=wx.ITEM_CHECK)

如果要附加复选菜单项,请将kind参数设置为wx.ITEM_CHECK。 默认参数是wx.ITEM_NORMALAppend()方法返回wx.MenuItem

viewMenu.Check(self.shst.GetId(), True)
viewMenu.Check(self.shtl.GetId(), True)

当应用启动时,状态栏和工具栏都可见。 因此,我们使用Check()方法检查两个菜单项。

def ToggleStatusBar(self, e):

    if self.shst.IsChecked():
        self.statusbar.Show()
    else:
        self.statusbar.Hide()

我们根据复选菜单项的状态显示或隐藏状态栏。 我们使用IsChecked()方法找出复选菜单项的状态。 与工具栏相同。

Check menu item

图:选中菜单项

上下文菜单

上下文菜单是在某些上下文下显示的命令列表。 例如,在 Firefox Web 浏览器中,当我们右键单击网页时,将获得一个上下文菜单。 在这里,我们可以重新加载页面,返回或查看页面源代码。 如果右键单击工具栏,则将获得另一个用于管理工具栏的上下文菜单。 上下文菜单有时称为弹出菜单。

context_menu.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a context menu.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class MyPopupMenu(wx.Menu):

    def __init__(self, parent):
        super(MyPopupMenu, self).__init__()

        self.parent = parent

        mmi = wx.MenuItem(self, wx.NewId(), 'Minimize')
        self.Append(mmi)
        self.Bind(wx.EVT_MENU, self.OnMinimize, mmi)

        cmi = wx.MenuItem(self, wx.NewId(), 'Close')
        self.Append(cmi)
        self.Bind(wx.EVT_MENU, self.OnClose, cmi)

    def OnMinimize(self, e):
        self.parent.Iconize()

    def OnClose(self, e):
        self.parent.Close()

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

        self.SetSize((350, 250))
        self.SetTitle('Context menu')
        self.Centre()

    def OnRightDown(self, e):
        self.PopupMenu(MyPopupMenu(self), e.GetPosition())

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在示例中,我们为主窗口创建一个上下文菜单。 它有两个项目。 一个将最小化应用,另一个将终止它。

class MyPopupMenu(wx.Menu):

    def __init__(self, parent):
        super(MyPopupMenu, self).__init__()

我们创建一个单独的wx.Menu类。

mmi = wx.MenuItem(self, wx.NewId(), 'Minimize')
self.Append(mmi)
self.Bind(wx.EVT_MENU, self.OnMinimize, mmi)

将创建一个菜单项并将其附加到上下文菜单。 事件处理器绑定到此菜单项。

self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)

如果我们右键单击框架,我们将调用OnRightDown()方法。 为此,我们使用wx.EVT_RIGHT_DOWN事件绑定器。

def OnRightDown(self, e):
    self.PopupMenu(MyPopupMenu(self), e.GetPosition())

OnRightDown()方法中,我们称为PopupMenu()方法。 此方法显示上下文菜单。 第一个参数是要显示的菜单。 第二个参数是上下文菜单出现的位置。 上下文菜单出现在鼠标光标的位置。 为了获得鼠标的实际位置,我们调用提供的事件对象的GetPosition()方法。

Context menu

图:上下文菜单

工具栏

菜单将我们可以在应用中使用的所有命令分组。 使用工具栏可以快速访问最常用的命令。

要创建工具栏,我们调用框架窗口小部件的CreateToolBar()方法。

toolbar.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example creates a simple toolbar.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        toolbar = self.CreateToolBar()
        qtool = toolbar.AddTool(wx.ID_ANY, 'Quit', wx.Bitmap('texit.png'))
        toolbar.Realize()

        self.Bind(wx.EVT_TOOL, self.OnQuit, qtool)

        self.SetSize((350, 250))
        self.SetTitle('Simple toolbar')
        self.Centre()

    def OnQuit(self, e):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们有一个带有一个工具的工具栏。 当我们单击该工具时,它将关闭该应用。

toolbar = self.CreateToolBar()

我们创建一个工具栏。 默认情况下,工具栏是水平的,没有边框并且显示图标。

qtool = toolbar.AddTool(wx.ID_ANY, 'Quit', wx.Bitmap('texit.png'))

要创建工具栏工具,我们调用AddTool()方法。 第二个参数是工具的标签,第三个参数是工具的图像。 请注意,标签不可见,因为默认样式仅显示图标。

toolbar.Realize()

将项目放入工具栏后,我们调用Realize()方法。 在 Linux 上,不必强制调用此方法。 在 Windows 上是。

self.Bind(wx.EVT_TOOL, self.OnQuit, qtool)

为了处理工具栏事件,我们使用wx.EVT_TOOL事件绑定器。

Simple toolbar

图:简单 toolbar

如果我们要创建多个工具栏,则必须以不同的方式进行。

toolbars.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
ZetCode wxPython tutorial

In this example, we create two horizontal
toolbars.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
'''

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        vbox = wx.BoxSizer(wx.VERTICAL)

        toolbar1 = wx.ToolBar(self)
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('tnew.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('topen.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('tsave.png'))
        toolbar1.Realize()

        toolbar2 = wx.ToolBar(self)
        qtool = toolbar2.AddTool(wx.ID_EXIT, '', wx.Bitmap('texit.png'))
        toolbar2.Realize()

        vbox.Add(toolbar1, 0, wx.EXPAND)
        vbox.Add(toolbar2, 0, wx.EXPAND)

        self.Bind(wx.EVT_TOOL, self.OnQuit, qtool)

        self.SetSizer(vbox)

        self.SetSize((350, 250))
        self.SetTitle('Toolbars')
        self.Centre()

    def OnQuit(self, e):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的示例中,我们创建了两个水平工具栏。

toolbar1 = wx.ToolBar(self)
... 
toolbar2 = wx.ToolBar(self)

我们创建两个工具栏对象。 并将它们放入垂直盒中。

Toolbars

图:工具栏 s

启用和禁用

在下面的示例中,我们展示了如何启用和禁用工具栏按钮。 我们还添加了分隔线。

undo_redo.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create two horizontal
toolbars.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        self.count = 5

        self.toolbar = self.CreateToolBar()
        tundo = self.toolbar.AddTool(wx.ID_UNDO, '', wx.Bitmap('tundo.png'))
        tredo = self.toolbar.AddTool(wx.ID_REDO, '', wx.Bitmap('tredo.png'))
        self.toolbar.EnableTool(wx.ID_REDO, False)
        self.toolbar.AddSeparator()
        texit = self.toolbar.AddTool(wx.ID_EXIT, '', wx.Bitmap('texit.png'))
        self.toolbar.Realize()

        self.Bind(wx.EVT_TOOL, self.OnQuit, texit)
        self.Bind(wx.EVT_TOOL, self.OnUndo, tundo)
        self.Bind(wx.EVT_TOOL, self.OnRedo, tredo)

        self.SetSize((350, 250))
        self.SetTitle('Undo redo')
        self.Centre()

    def OnUndo(self, e):
        if self.count > 1 and self.count <= 5:
            self.count = self.count - 1

        if self.count == 1:
            self.toolbar.EnableTool(wx.ID_UNDO, False)

        if self.count == 4:
            self.toolbar.EnableTool(wx.ID_REDO, True)

    def OnRedo(self, e):
        if self.count < 5 and self.count >= 1:
            self.count = self.count + 1

        if self.count == 5:
            self.toolbar.EnableTool(wx.ID_REDO, False)

        if self.count == 2:
            self.toolbar.EnableTool(wx.ID_UNDO, True)

    def OnQuit(self, e):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们有三个工具栏按钮。 一个按钮用于退出应用。 其他两个按钮是撤消和重做按钮。 它们模拟应用中的撤消/重做功能。 (有关真实示例,请参见技巧)。我们进行了 4 个更改。 撤消和重做按钮相应地被禁用。

self.toolbar.EnableTool(wx.ID_REDO, False)
self.toolbar.AddSeparator()

首先,重做按钮被禁用。 我们通过调用EnableTool()方法来实现。 我们可以在工具栏中创建一些逻辑组。 我们可以用一条小的垂直线将各种按钮组分开。 为此,我们调用AddSeparator()方法。

def OnUndo(self, e):
    if self.count > 1 and self.count <= 5:
        self.count = self.count - 1

    if self.count == 1:
        self.toolbar.EnableTool(wx.ID_UNDO, False)

    if self.count == 4:
        self.toolbar.EnableTool(wx.ID_REDO, True)

我们模拟撤消和重做功能。 我们有四个变化。 如果没有什么可撤消的,撤消按钮将被禁用。 撤消第一个更改后,我们启用重做按钮。 相同的逻辑适用于OnRedo()方法。

Undo redo

图:撤销和重做

在 wxPython 教程的这一部分中,我们使用了菜单和工具栏。

wxPython 中的布局管理

原文: http://zetcode.com/wxpython/layout/

典型的应用由各种小部件组成。 这些小部件放置在容器小部件内。 程序员必须管理应用的布局。 这不是一件容易的事。 在 wxPython 中,可以使用绝对定位或使用大小调整器来布局小部件。

绝对定位

程序员以像素为单位指定每个小部件的位置和大小。 绝对定位有几个缺点:

  • 如果我们调整窗口大小,则小部件的大小和位置不会改变。
  • 在各种平台上,应用看起来都不同。
  • 在应用中更改字体可能会破坏布局。
  • 如果决定更改布局,则必须完全重做布局,这既繁琐又耗时。

在某些情况下,我们可能会使用绝对定位。 例如,小的测试示例。 但是大多数情况下,在现实世界的程序中,程序员使用大小调整器。

在我们的示例中,我们有一个简单的文本编辑器框架。 如果我们调整窗口大小,则wx.TextCtrl的大小不会像我们期望的那样改变。

absolute.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we lay out widgets using
absolute positioning.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title,
            size=(350, 300))

        self.InitUI()
        self.Centre()

    def InitUI(self):

        self.panel = wx.Panel(self)

        self.panel.SetBackgroundColour("gray")

        self.LoadImages()

        self.mincol.SetPosition((20, 20))
        self.bardejov.SetPosition((40, 160))
        self.rotunda.SetPosition((170, 50))

    def LoadImages(self):

        self.mincol = wx.StaticBitmap(self.panel, wx.ID_ANY,
            wx.Bitmap("mincol.jpg", wx.BITMAP_TYPE_ANY))

        self.bardejov = wx.StaticBitmap(self.panel, wx.ID_ANY,
            wx.Bitmap("bardejov.jpg", wx.BITMAP_TYPE_ANY))

        self.rotunda = wx.StaticBitmap(self.panel, wx.ID_ANY,
            wx.Bitmap("rotunda.jpg", wx.BITMAP_TYPE_ANY))

def main():

    app = wx.App()
    ex = Example(None, title='Absolute positioning')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的示例中,我们使用绝对坐标定位了三个图像。

self.mincol.SetPosition((20, 20))

使用SetPosition()方法,我们将图像放置在x = 20y = 20坐标处。

absolute positioning 1

图:绝对定位

使用大小调整器

大小调整器确实解决了绝对定位中提到的所有这些问题。 wxPython 具有以下大小调整器:

  • wx.BoxSizer
  • wx.StaticBoxSizer
  • wx.GridSizer
  • wx.FlexGridSizer
  • wx.GridBagSizer

wx.BoxSizer

wx.BoxSizer使我们可以将几个小部件放在一行或一列中。 我们可以将另一个调整器放到现有的调整器中。 这样,我们可以创建非常复杂的布局。

 box = wx.BoxSizer(integer orient)
 box.Add(wx.Window window, integer proportion=0, integer flag = 0, integer border = 0)

方向可以是wx.VERTICALwx.HORIZONTAL。 通过Add()方法将小部件添加到wx.BoxSizer中。 为了理解它,我们需要查看它的参数。

比例参数定义小部件在定义的方向上如何变化的比例。 假设我们有三个比例分别为 0、1 和 2 的按钮。它们被添加到水平wx.BoxSizer中。 比例为 0 的按钮完全不会改变。 在水平方向上比例为 2 的按钮的变化比比例为 1 的按钮大两倍。

使用flag参数,您可以进一步在wx.BoxSizer中配置小部件的行为。 我们可以控制小部件之间的边界。 我们在小部件之间添加一些像素间距。 为了应用边框,我们需要定义要使用边框的边。 我们可以将它们与|运算符结合使用; 例如wx.LEFT | wx.BOTTOM。 我们可以在这些标志之间进行选择:

  • wx.LEFT
  • wx.RIGHT
  • wx.TOP
  • wx.BOTTOM
  • wx.ALL

通过setSizer()方法将大小调整器设置为面板小部件。

border.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we place a panel inside 
another panel.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title)

        self.InitUI()
        self.Centre()

    def InitUI(self):

        panel = wx.Panel(self)

        panel.SetBackgroundColour('#4f5049')
        vbox = wx.BoxSizer(wx.VERTICAL)

        midPan = wx.Panel(panel)
        midPan.SetBackgroundColour('#ededed')

        vbox.Add(midPan, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)
        panel.SetSizer(vbox)

def main():

    app = wx.App()
    ex = Example(None, title='Border')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的示例中,我们在面板周围放置了一些空间。

vbox.Add(midPan, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)

border.py中,我们在midPan面板周围放置了 20 像素边框。 wx.ALL将边框大小应用于所有四个侧面。

如果我们使用wx.EXPAND标志,则我们的窗口小部件将使用分配给它的所有空间。 最后,我们还可以定义小部件的对齐方式。 我们使用以下标志来实现:

  • wx.ALIGN_LEFT
  • wx.ALIGN_RIGHT
  • wx.ALIGN_TOP
  • wx.ALIGN_BOTTOM
  • wx.ALIGN_CENTER_VERTICAL
  • wx.ALIGN_CENTER_HORIZONTAL
  • wx.ALIGN_CENTER

Border around a panel

图:面板周围的边框

GoToClass示例

在下面的示例中,我们介绍了几个重要的想法。

goto_class.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create a Go To class
layout with wx.BoxSizer.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title)

        self.InitUI()
        self.Centre()

    def InitUI(self):

        panel = wx.Panel(self)

        font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)

        font.SetPointSize(9)

        vbox = wx.BoxSizer(wx.VERTICAL)

        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        st1 = wx.StaticText(panel, label='Class Name')
        st1.SetFont(font)
        hbox1.Add(st1, flag=wx.RIGHT, border=8)
        tc = wx.TextCtrl(panel)
        hbox1.Add(tc, proportion=1)
        vbox.Add(hbox1, flag=wx.EXPAND|wx.LEFT|wx.RIGHT|wx.TOP, border=10)

        vbox.Add((-1, 10))

        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        st2 = wx.StaticText(panel, label='Matching Classes')
        st2.SetFont(font)
        hbox2.Add(st2)
        vbox.Add(hbox2, flag=wx.LEFT | wx.TOP, border=10)

        vbox.Add((-1, 10))

        hbox3 = wx.BoxSizer(wx.HORIZONTAL)
        tc2 = wx.TextCtrl(panel, style=wx.TE_MULTILINE)
        hbox3.Add(tc2, proportion=1, flag=wx.EXPAND)
        vbox.Add(hbox3, proportion=1, flag=wx.LEFT|wx.RIGHT|wx.EXPAND,
            border=10)

        vbox.Add((-1, 25))

        hbox4 = wx.BoxSizer(wx.HORIZONTAL)
        cb1 = wx.CheckBox(panel, label='Case Sensitive')
        cb1.SetFont(font)
        hbox4.Add(cb1)
        cb2 = wx.CheckBox(panel, label='Nested Classes')
        cb2.SetFont(font)
        hbox4.Add(cb2, flag=wx.LEFT, border=10)
        cb3 = wx.CheckBox(panel, label='Non-Project classes')
        cb3.SetFont(font)
        hbox4.Add(cb3, flag=wx.LEFT, border=10)
        vbox.Add(hbox4, flag=wx.LEFT, border=10)

        vbox.Add((-1, 25))

        hbox5 = wx.BoxSizer(wx.HORIZONTAL)
        btn1 = wx.Button(panel, label='Ok', size=(70, 30))
        hbox5.Add(btn1)
        btn2 = wx.Button(panel, label='Close', size=(70, 30))
        hbox5.Add(btn2, flag=wx.LEFT|wx.BOTTOM, border=5)
        vbox.Add(hbox5, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=10)

        panel.SetSizer(vbox)

def main():

    app = wx.App()
    ex = Example(None, title='Go To Class')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

布局僵硬。 我们创建一个垂直大小调整器。 然后,我们将五个水平大小调整器放入其中。

font = wx.SystemSettings.GetFont(wx.SYS_SYSTEM_FONT)

font.SetPointSize(9)

我们将字体大小更改为 9 像素。

vbox.Add(hbox3, proportion=1, flag=wx.LEFT|wx.RIGHT|wx.EXPAND, 
    border=10)

vbox.Add((-1, 25))

我们已经知道可以通过组合flag参数和border参数来控制小部件之间的距离。 但是有一个真正的约束。 在Add()方法中,我们只能为所有给定的边指定一个边框。 在我们的示例中,我们在右侧和左侧分别设置了 10 像素。 但是我们不能给底部 25 像素。 我们可以做的是在底部给 10px,如果省略wx.BOTTOM则给 0px。 因此,如果我们需要不同的值,则可以添加一些额外的空间。 使用Add()方法,我们也可以插入小部件和空间。

vbox.Add(hbox5, flag=wx.ALIGN_RIGHT|wx.RIGHT, border=10)

我们将两个按钮放在窗口的右侧。 实现这一点很重要的三件事:比例,对齐标志和wx.EXPAND标志。 比例必须为零。 调整窗口大小时,按钮的大小不应更改。 我们一定不要指定wx.EXPAND标志。 这些按钮仅占用分配给它们的区域。 最后,我们必须指定wx.ALIGN_RIGHT标志。 水平大小调整器从窗口的左侧扩展到右侧。 因此,如果我们指定wx.ALIGN_RIGHT标志,则按钮将放置在右侧。

GoToClass window

图:GoToClass窗口

wx.GridSizer

wx.GridSizer在二维表中布置小部件。 表格中的每个单元格都具有相同的大小。

wx.GridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)

在构造器中,我们指定表中的行和列数以及单元格之间的垂直和水平空间。

在我们的示例中,我们创建了计算器的骨架。

calculator.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create a layout
of a calculator with wx.GridSizer.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title)

        self.InitUI()
        self.Centre()

    def InitUI(self):

        menubar = wx.MenuBar()
        fileMenu = wx.Menu()
        menubar.Append(fileMenu, '&File')
        self.SetMenuBar(menubar)

        vbox = wx.BoxSizer(wx.VERTICAL)
        self.display = wx.TextCtrl(self, style=wx.TE_RIGHT)
        vbox.Add(self.display, flag=wx.EXPAND|wx.TOP|wx.BOTTOM, border=4)
        gs = wx.GridSizer(5, 4, 5, 5)

        gs.AddMany( [(wx.Button(self, label='Cls'), 0, wx.EXPAND),
            (wx.Button(self, label='Bck'), 0, wx.EXPAND),
            (wx.StaticText(self), wx.EXPAND),
            (wx.Button(self, label='Close'), 0, wx.EXPAND),
            (wx.Button(self, label='7'), 0, wx.EXPAND),
            (wx.Button(self, label='8'), 0, wx.EXPAND),
            (wx.Button(self, label='9'), 0, wx.EXPAND),
            (wx.Button(self, label='/'), 0, wx.EXPAND),
            (wx.Button(self, label='4'), 0, wx.EXPAND),
            (wx.Button(self, label='5'), 0, wx.EXPAND),
            (wx.Button(self, label='6'), 0, wx.EXPAND),
            (wx.Button(self, label='*'), 0, wx.EXPAND),
            (wx.Button(self, label='1'), 0, wx.EXPAND),
            (wx.Button(self, label='2'), 0, wx.EXPAND),
            (wx.Button(self, label='3'), 0, wx.EXPAND),
            (wx.Button(self, label='-'), 0, wx.EXPAND),
            (wx.Button(self, label='0'), 0, wx.EXPAND),
            (wx.Button(self, label='.'), 0, wx.EXPAND),
            (wx.Button(self, label='='), 0, wx.EXPAND),
            (wx.Button(self, label='+'), 0, wx.EXPAND) ])

        vbox.Add(gs, proportion=1, flag=wx.EXPAND)
        self.SetSizer(vbox)

def main():

    app = wx.App()
    ex = Example(None, title='Calculator')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

注意我们如何设法在返回和关闭按钮之间放置一个空格。 我们只需在其中放置一个空的wx.StaticText

在我们的示例中,我们使用了AddMany()方法。 这是一次同时添加多个小部件的便捷方法。

gs.AddMany( [(wx.Button(self, label='Cls'), 0, wx.EXPAND),
...

将小部件按顺序放置在表中,然后将它们添加。 第一行先填充,然后第二行等。

Calculator

图:计算器

wx.FlexGridSizer

该大小调整器类似于wx.GridSizer。 它还确实将其小部件布置在二维表中。 它增加了一些灵活性。 wx.GridSizer细胞大小相同。 wx.FlexGridSizer中的所有单元格都具有相同的高度。 一列中所有单元格的宽度均相同。 但是,所有行和列不一定都具有相同的高度或宽度。

wx.FlexGridSizer(int rows=1, int cols=0, int vgap=0, int hgap=0)

rowscols指定大小调整器中的行数和列数。 vgaphgap在两个方向的小部件之间添加了一些空间。

很多时候,开发者必须开发用于数据输入和修改的对话框。 我发现wx.FlexGridSizer适用于此类任务。 开发者可以使用此 sizer 轻松设置对话框窗口。 也可以使用wx.GridSizer完成此操作,但由于每个单元格必须具有相同的大小的限制,因此看起来不太好。

review.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create review
layout with wx.FlexGridSizer.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title)

        self.InitUI()
        self.Centre()
        self.Show()

    def InitUI(self):

        panel = wx.Panel(self)

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        fgs = wx.FlexGridSizer(3, 2, 9, 25)

        title = wx.StaticText(panel, label="Title")
        author = wx.StaticText(panel, label="Author")
        review = wx.StaticText(panel, label="Review")

        tc1 = wx.TextCtrl(panel)
        tc2 = wx.TextCtrl(panel)
        tc3 = wx.TextCtrl(panel, style=wx.TE_MULTILINE)

        fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author),
            (tc2, 1, wx.EXPAND), (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)])

        fgs.AddGrowableRow(2, 1)
        fgs.AddGrowableCol(1, 1)

        hbox.Add(fgs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15)
        panel.SetSizer(hbox)

def main():

    app = wx.App()
    ex = Example(None, title='Review')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的代码示例中,我们使用FlexGridSizer创建一个Review窗口。

hbox = wx.BoxSizer(wx.HORIZONTAL)
...
hbox.Add(fgs, proportion=1, flag=wx.ALL|wx.EXPAND, border=15)

我们创建一个水平框大小调整器,以便在小部件表周围放置一些空间(15 像素)。

fgs.AddMany([(title), (tc1, 1, wx.EXPAND), (author), 
    (tc2, 1, wx.EXPAND), (review, 1, wx.EXPAND), (tc3, 1, wx.EXPAND)])

我们使用AddMany()方法将小部件添加到大小调整器中。 wx.FlexGridSizerwx.GridSizer都共享此方法。

fgs.AddGrowableRow(2, 1)
fgs.AddGrowableCol(1, 1)

我们使第三行和第二列可增长。 这样,当调整窗口大小时,我们使文本控件增加。 前两个文本控件将在水平方向上增长,第三个文本控件将在两个方向上增长。 我们一定不要忘记使小部件可以使用wx.EXPAND进行扩展以使其起作用。

Review

图:回顾 example

wx.GridBagSizer

wx.GridBagSizer是 wxPython 中最灵活的大小调整器。 这种大小调整器不仅仅适用于 wxPython。 我们也可以在其他工具包中找到它。

该大小调整器可以显式定位项目。 项还可以选择跨越多个行或一列。 wx.GridBagSizer具有简单的构造器。

wx.GridBagSizer(integer vgap, integer hgap)

垂直和水平间隙定义了所有子级之间使用的像素间隔。 我们使用Add()方法将项目添加到网格中。

Add(self, item, tuple pos, tuple span=wx.DefaultSpan, integer flag=0, 
    integer border=0, userData=None)

Item是插入网格中的小部件。 pos 指定虚拟网格中的位置。 左上角的单元格的位置为(0,0)。 范围是窗口小部件的可选范围; 例如 (3,2)的范围跨 3 行和 2 列跨越一个窗口小部件。 wx.BoxSizer前面已经讨论了标志和边界。 调整窗口大小时,网格中的项目可以更改其大小或保留默认大小。 如果我们希望您的项目增加和缩小,可以使用以下两种方法:

AddGrowableRow(integer row)
AddGrowableCol(integer col)

重命名窗口示例

在第一个示例中,我们创建一个“重命名”窗口。 它将具有一个wx.StaticText,一个wx.TextCtrl和两个wx.Button小部件。

rename.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create a rename layout
with wx.GridBagSizer.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title)

        self.InitUI()
        self.Centre()

    def InitUI(self):

        panel = wx.Panel(self)
        sizer = wx.GridBagSizer(4, 4)

        text = wx.StaticText(panel, label="Rename To")
        sizer.Add(text, pos=(0, 0), flag=wx.TOP|wx.LEFT|wx.BOTTOM, border=5)

        tc = wx.TextCtrl(panel)
        sizer.Add(tc, pos=(1, 0), span=(1, 5),
            flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5)

        buttonOk = wx.Button(panel, label="Ok", size=(90, 28))
        buttonClose = wx.Button(panel, label="Close", size=(90, 28))
        sizer.Add(buttonOk, pos=(3, 3))
        sizer.Add(buttonClose, pos=(3, 4), flag=wx.RIGHT|wx.BOTTOM, border=10)

        sizer.AddGrowableCol(1)
        sizer.AddGrowableRow(2)
        panel.SetSizer(sizer)

def main():

    app = wx.App()
    ex = Example(None, title='Rename')
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们必须将窗口视为一个大的网格表。

text = wx.StaticText(panel, label="Rename To")
sizer.Add(text, pos=(0, 0), flag=wx.TOP|wx.LEFT|wx.BOTTOM, border=10)

文本“重命名为”转到左上角。 因此,我们指定(0,0)位置。 然后在底部,左侧和底部添加一些空间。

tc = wx.TextCtrl(panel)
sizer.Add(tc, pos=(1, 0), span=(1, 5), 
    flag=wx.EXPAND|wx.LEFT|wx.RIGHT, border=5)

wx.TextCtrl转到第二行的开头(1、0)。 请记住,我们从零开始计数。 它扩展了 1 行和 5 列(1、5)。 然后,在小部件的左侧和右侧放置 5 个像素的空间。

sizer.Add(buttonOk, pos=(3, 3))
sizer.Add(buttonClose, pos=(3, 4), flag=wx.RIGHT|wx.BOTTOM, border=10)

我们在第四行中放置了两个按钮。 第三行留空,以便我们在wx.TextCtrl和按钮之间留出一些空间。 我们将“确定”按钮放入第四列,将“关闭”按钮放入第五列。 请注意,一旦我们向一个小部件应用了一些空间,它就会被应用于整行。 这就是为什么我们没有为“确定”按钮指定底部空间的原因。 细心的读者可能会注意到,我们没有在两个按钮之间指定任何空格。 也就是说,我们没有在“确定”按钮的右侧或“关闭”按钮的右侧放置任何空格。 在wx.GridBagSizer的构造器中,我们在所有小部件之间放置了一些空间。 所以已经有一些空间了。

sizer.AddGrowableCol(1)
sizer.AddGrowableRow(2)

我们必须做的最后一件事是使对话框可调整大小。 我们使第二列和第三行可增长。 现在我们可以扩大或缩小窗口。 尝试注释这两行,然后看看会发生什么。

Rename

图:重命名窗口

新类示例

在下一个示例中,我们创建一个窗口,可以在 JDeveloper 中找到它。 这是用于在 Java 中创建新类的窗口。

new_class.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create a new class layout
with wx.GridBagSizer.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, parent, title):
        super(Example, self).__init__(parent, title=title)

        self.InitUI()
        self.Centre()

    def InitUI(self):

        panel = wx.Panel(self)

        sizer = wx.GridBagSizer(5, 5)

        text1 = wx.StaticText(panel, label="Java Class")
        sizer.Add(text1, pos=(0, 0), flag=wx.TOP|wx.LEFT|wx.BOTTOM,
            border=15)

        icon = wx.StaticBitmap(panel, bitmap=wx.Bitmap('exec.png'))
        sizer.Add(icon, pos=(0, 4), flag=wx.TOP|wx.RIGHT|wx.ALIGN_RIGHT,
            border=5)

        line = wx.StaticLine(panel)
        sizer.Add(line, pos=(1, 0), span=(1, 5),
            flag=wx.EXPAND|wx.BOTTOM, border=10)

        text2 = wx.StaticText(panel, label="Name")
        sizer.Add(text2, pos=(2, 0), flag=wx.LEFT, border=10)

        tc1 = wx.TextCtrl(panel)
        sizer.Add(tc1, pos=(2, 1), span=(1, 3), flag=wx.TOP|wx.EXPAND)

        text3 = wx.StaticText(panel, label="Package")
        sizer.Add(text3, pos=(3, 0), flag=wx.LEFT|wx.TOP, border=10)

        tc2 = wx.TextCtrl(panel)
        sizer.Add(tc2, pos=(3, 1), span=(1, 3), flag=wx.TOP|wx.EXPAND,
            border=5)

        button1 = wx.Button(panel, label="Browse...")
        sizer.Add(button1, pos=(3, 4), flag=wx.TOP|wx.RIGHT, border=5)

        text4 = wx.StaticText(panel, label="Extends")
        sizer.Add(text4, pos=(4, 0), flag=wx.TOP|wx.LEFT, border=10)

        combo = wx.ComboBox(panel)
        sizer.Add(combo, pos=(4, 1), span=(1, 3),
            flag=wx.TOP|wx.EXPAND, border=5)

        button2 = wx.Button(panel, label="Browse...")
        sizer.Add(button2, pos=(4, 4), flag=wx.TOP|wx.RIGHT, border=5)

        sb = wx.StaticBox(panel, label="Optional Attributes")

        boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)
        boxsizer.Add(wx.CheckBox(panel, label="Public"),
            flag=wx.LEFT|wx.TOP, border=5)
        boxsizer.Add(wx.CheckBox(panel, label="Generate Default Constructor"),
            flag=wx.LEFT, border=5)
        boxsizer.Add(wx.CheckBox(panel, label="Generate Main Method"),
            flag=wx.LEFT|wx.BOTTOM, border=5)
        sizer.Add(boxsizer, pos=(5, 0), span=(1, 5),
            flag=wx.EXPAND|wx.TOP|wx.LEFT|wx.RIGHT , border=10)

        button3 = wx.Button(panel, label='Help')
        sizer.Add(button3, pos=(7, 0), flag=wx.LEFT, border=10)

        button4 = wx.Button(panel, label="Ok")
        sizer.Add(button4, pos=(7, 3))

        button5 = wx.Button(panel, label="Cancel")
        sizer.Add(button5, pos=(7, 4), span=(1, 1),
            flag=wx.BOTTOM|wx.RIGHT, border=10)

        sizer.AddGrowableCol(2)

        panel.SetSizer(sizer)
        sizer.Fit(self)

def main():

    app = wx.App()
    ex = Example(None, title="Create Java Class")
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

这是一个更复杂的布局。 我们同时使用wx.GridBagSizerwx.StaticBoxsizer

line = wx.StaticLine(panel)
sizer.Add(line, pos=(1, 0), span=(1, 5), 
    flag=wx.EXPAND|wx.BOTTOM, border=10)

该行用于分隔布局中的小部件组。

icon = wx.StaticBitmap(panel, bitmap=wx.Bitmap('exec.png'))
sizer.Add(icon, pos=(0, 4), flag=wx.TOP|wx.RIGHT|wx.ALIGN_RIGHT, 
    border=5)

我们将wx.StaticBitmap放入网格的第一行。 我们将其放在行的右侧。

sb = wx.StaticBox(panel, label="Optional Attributes")
boxsizer = wx.StaticBoxSizer(sb, wx.VERTICAL)

wxStaticBoxSizer类似于普通的wx.BoxSizer,但它在大小调整器周围添加了一个静态框。 我们将复选框放入静态框大小调整器中。

New class window

图:新类窗口

wxPython 教程的这一部分专门用于布局管理。

wxPython 中的事件

原文: http://zetcode.com/wxpython/events/

事件是每个 GUI 应用不可或缺的一部分。 所有 GUI 应用都是事件驱动的。 应用会对在其生命周期内生成的不同事件类型做出反应。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成。 例如互联网连接,窗口管理器或计时器。 因此,当我们调用MainLoop()方法时,我们的应用将等待事件生成。 当我们退出应用时,MainLoop()方法结束。

定义

事件是来自底层框架(通常是 GUI 工具箱)的应用级信息。事件循环是一种程序结构,用于等待并调度器中的事件或消息。 事件循环反复查找要处理的事件。调度器是将事件映射到事件处理器的过程。 事件处理器是对事件做出反应的方法。

事件对象是与事件关联的对象。 通常是一个窗口。事件类型是已生成的唯一事件。事件绑定器是将事件类型与事件处理器绑定在一起的对象。

wxPython wx.EVT_MOVE示例

在下面的示例中,我们对wx.MoveEvent事件做出反应。 当我们将窗口移到新位置时会生成该事件。 此事件的事件绑定器为wx.EVT_MOVE

simple_event.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This is a wx.MoveEvent event demostration.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        wx.StaticText(self, label='x:', pos=(10,10))
        wx.StaticText(self, label='y:', pos=(10,30))

        self.st1 = wx.StaticText(self, label='', pos=(30, 10))
        self.st2 = wx.StaticText(self, label='', pos=(30, 30))

        self.Bind(wx.EVT_MOVE, self.OnMove)

        self.SetSize((350, 250))
        self.SetTitle('Move event')
        self.Centre()

    def OnMove(self, e):

        x, y = e.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main() 

该示例显示窗口的当前位置。

self.Bind(wx.EVT_MOVE, self.OnMove)

在这里,我们将wx.EVT_MOVE事件绑定器绑定到OnMove()方法。

def OnMove(self, e):

    x, y = e.GetPosition()
    self.st1.SetLabel(str(x))
    self.st2.SetLabel(str(y))

OnMove()方法中的事件参数是特定于特定事件类型的对象。 在我们的例子中,它是wx.MoveEvent类的实例。 该对象保存有关事件的信息。 例如事件对象或窗口的位置。 在我们的例子中,事件对象是wx.Frame小部件。 我们可以通过调用事件的GetPosition()方法找出当前位置。

Move event

图:移动事件

wxPython 事件绑定

在 wxPython 中使用事件的三个​​步骤是:

  • 标识事件绑定程序名称:wx.EVT_SIZEwx.EVT_CLOSE等。
  • 创建一个事件处理器。 生成事件时将调用此方法。
  • 将事件绑定到事件处理器。

在 wxPython 中,我们说将方法绑定到事件。 有时会使用单词钩子。 您可以通过调用Bind()方法来绑定事件。 该方法具有以下参数:

Bind(event, handler, source=None, id=wx.ID_ANY, id2=wx.ID_ANY)

eventEVT*对象之一。 它指定事件的类型。 handler是要调用的对象。 换句话说,它是程序员绑定到事件的一种方法。 当我们要区分相同事件类型和不同小部件时,使用source参数。 当我们有多个按钮,菜单项等时,将使用id参数。id用于在它们之间进行区分。 当需要将处理器绑定到 ID 范围时,例如EVT_MENU_RANGE,可以使用id2

注意,方法Bind()在类EvtHandler中定义。 它是wx.Window继承的类。 wx.Window是 wxPython 中大多数小部件的基类。 还有一个相反的过程。 如果要从事件中取消绑定方法,请调用Unbind()方法。 它具有与上述相同的参数。

取消事件

有时我们需要停止处理事件。 为此,我们称方法Veto()

event_veto.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import wx

"""
ZetCode wxPython tutorial

In this example we veto an event.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_CLOSE, self.OnCloseWindow)

        self.SetTitle('Event veto')
        self.Centre()

    def OnCloseWindow(self, e):

        dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
            wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)

        ret = dial.ShowModal()

        if ret == wx.ID_YES:
            self.Destroy()
        else:
            e.Veto()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们处理wx.CloseEvent。 当我们单击标题栏上的 X 按钮,按 Alt + F4 或从系统菜单中选择关闭时,将称为此事件。 在许多应用中,如果要进行一些更改,我们希望防止意外关闭窗口。 为此,我们必须绑定wx.EVT_CLOSE事件绑定器。

dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
    wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)

ret = dial.ShowModal()

在处理关闭事件期间,我们显示一个消息对话框。

if ret == wx.ID_YES:
    self.Destroy()
else:
    event.Veto()

根据对话框的返回值,我们销毁窗口或否决事件。 注意,要关闭窗口,我们必须调用Destroy()方法。 通过调用Close()方法,我们将陷入无尽的循环。

wxPython 事件传播

事件有两种类型:基本事件和命令事件。 它们的传播方式不同。 事件传播是事件从子窗口小部件到父窗口小部件和祖父窗口小部件的传播。 基本事件不会传播。 命令事件确实传播。 例如,wx.CloseEvent是一个基本事件。 此事件传播到父窗口小部件没有任何意义。

默认情况下,在事件处理器中捕获的事件停止传播。 为了继续传播,我们调用Skip()方法。

event_propagation.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example demonstrates event propagation.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class MyPanel(wx.Panel):

    def __init__(self, *args, **kw):
        super(MyPanel, self).__init__(*args, **kw)

        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

    def OnButtonClicked(self, e):

        print('event reached panel class')
        e.Skip()

class MyButton(wx.Button):

    def __init__(self, *args, **kw):
        super(MyButton, self).__init__(*args, **kw)

        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

    def OnButtonClicked(self, e):

        print('event reached button class')
        e.Skip()

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        mpnl = MyPanel(self)

        MyButton(mpnl, label='Ok', pos=(15, 15))

        self.Bind(wx.EVT_BUTTON, self.OnButtonClicked)

        self.SetTitle('Propagate event')
        self.Centre()

    def OnButtonClicked(self, e):

        print('event reached frame class')
        e.Skip()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,面板上有一个按钮。 面板放置在框架小部件中。 我们为所有小部件定义一个处理器。

def OnButtonClicked(self, e):

    print('event reached button class')
    e.Skip()

我们在自定义按钮类中处理按钮单击事件。 Skip()方法将事件进一步传播到面板类。

尝试省略一些Skip()方法,看看会发生什么。

窗口标识符

窗口标识符是在事件系统中唯一确定窗口标识的整数。 有三种创建窗口 ID 的方法。

  • 让系统自动创建一个 ID
  • 使用标准标识符
  • 创建自己的 ID
wx.Button(parent, -1)
wx.Button(parent, wx.ID_ANY)

如果为 id 参数提供 -1 或wx.ID_ANY,则让 wxPython 自动为我们创建一个 id。 自动创建的 ID 始终为负,而用户指定的 ID 必须始终为正。 当我们不需要更改窗口小部件状态时,通常使用此选项。 例如,静态文本在应用的生命周期内将永远不会更改。 如果需要,我们仍然可以获取 ID。 有一种确定 ID 的方法GetId()

default_ids.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we use automatic ids
with wx.ID_ANY.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        exitButton = wx.Button(pnl, wx.ID_ANY, 'Exit', (10, 10))

        self.Bind(wx.EVT_BUTTON,  self.OnExit, id=exitButton.GetId())

        self.SetTitle("Automatic ids")
        self.Centre()

    def OnExit(self, event):

        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,我们不在乎实际的 ID 值。

self.Bind(wx.EVT_BUTTON,  self.OnExit, id=exitButton.GetId())

我们通过调用GetId()方法来获取自动生成的 ID。

建议使用标准标识符。 标识符可以在某些平台上提供一些标准的图形或行为。

wxPython 标准 ID

wxPython 包含一些标准 ID,例如wx.ID_SAVEwx.ID_NEW

standard_ids.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create buttons with standard ids.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        grid = wx.GridSizer(3, 2)

        grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
            (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9),
            (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9),
            (wx.Button(pnl, wx.ID_EXIT)),
            (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9),
            (wx.Button(pnl, wx.ID_NEW))])

        self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)

        pnl.SetSizer(grid)

        self.SetTitle("Standard ids")
        self.Centre()

    def OnQuitApp(self, event):

        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们在按钮上使用标准标识符。 在 Linux 上,按钮带有图标。

grid.AddMany([(wx.Button(pnl, wx.ID_CANCEL), 0, wx.TOP | wx.LEFT, 9),
    (wx.Button(pnl, wx.ID_DELETE), 0, wx.TOP, 9),
    (wx.Button(pnl, wx.ID_SAVE), 0, wx.LEFT, 9),
    (wx.Button(pnl, wx.ID_EXIT)),
    (wx.Button(pnl, wx.ID_STOP), 0, wx.LEFT, 9),
    (wx.Button(pnl, wx.ID_NEW))])

我们向网格大小调整器添加六个按钮。 wx.ID_CANCELwx.ID_DELETEwx.ID_SAVEwx.ID_EXITwx.ID_STOPwx.ID_NEW是标准标识符。

self.Bind(wx.EVT_BUTTON, self.OnQuitApp, id=wx.ID_EXIT)

我们将按钮单击事件绑定到OnQuitApp()事件处理器。 id参数用于区分按钮。 我们唯一地标识事件的来源。

Standard identifiers

图:标准标识符

自定义事件 ID

最后一个选择是使用自己的标识符。 我们定义了自己的全局 ID。

custom_ids.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we use custom event ids.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

ID_MENU_NEW = wx.NewId()
ID_MENU_OPEN = wx.NewId()
ID_MENU_SAVE = wx.NewId()

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.CreateMenuBar()
        self.CreateStatusBar()

        self.SetSize((350, 250))
        self.SetTitle('Custom ids')
        self.Centre()

    def CreateMenuBar(self):

        mb = wx.MenuBar()

        fMenu = wx.Menu()
        fMenu.Append(ID_MENU_NEW, 'New')
        fMenu.Append(ID_MENU_OPEN, 'Open')
        fMenu.Append(ID_MENU_SAVE, 'Save')

        mb.Append(fMenu, '&File')
        self.SetMenuBar(mb)

        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN)
        self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE)

    def DisplayMessage(self, e):

        sb = self.GetStatusBar()

        eid = e.GetId()

        if eid == ID_MENU_NEW:
            msg = 'New menu item selected'
        elif eid == ID_MENU_OPEN:
            msg = 'Open menu item selected'
        elif eid == ID_MENU_SAVE:
            msg = 'Save menu item selected'

        sb.SetStatusText(msg)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main() 

在代码示例中,我们创建一个包含三个菜单项的菜单。 此菜单项的 ID 是全局创建的。

ID_MENU_NEW = wx.NewId()
ID_MENU_OPEN = wx.NewId()
ID_MENU_SAVE = wx.NewId()

wx.NewId()方法创建一个新的唯一 ID。

self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_NEW)
self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_OPEN)
self.Bind(wx.EVT_MENU, self.DisplayMessage, id=ID_MENU_SAVE) 

这三个菜单项均由其唯一 ID 标识。

eid = e.GetId()

if eid == ID_MENU_NEW:
    msg = 'New menu item selected'
elif eid == ID_MENU_OPEN:
    msg = 'Open menu item selected'
elif eid == ID_MENU_SAVE:
    msg = 'Save menu item selected'

从事件对象中,我们检索 ID。 根据 ID 值,我们准备消息,该消息显示在应用的状态栏中。

wx.PaintEvent

重绘窗口时会生成一次绘制事件。 当我们调整窗口大小或最大化窗口时,会发生这种情况。 绘图事件也可以通过编程方式生成。 例如,当我们调用SetLabel()方法来更改wx.StaticText小部件时。 请注意,当我们最小化窗口时,不会生成任何绘制事件。

paint_event.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we count paint events.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.count = 0
        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle('Paint events')
        self.SetSize((350, 250))
        self.Centre()

    def OnPaint(self, e):

        self.count += 1
        dc = wx.PaintDC(self)
        text = "Number of paint events: {0}".format(self.count)
        dc.DrawText(text, 20, 20)

def main():

    app = wx.App()
    ex  = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们计算绘制事件的数量并在窗口上绘制当前生成的事件的数量。

self.Bind(wx.EVT_PAINT, self.OnPaint)

我们将EVT_PAINT事件绑定到OnPaint()方法。

def OnPaint(self, e):

    self.count += 1
    dc = wx.PaintDC(self)
    text = "Number of paint events: {0}".format(self.count)
    dc.DrawText(text, 20, 20)

OnPaint()事件内部,我们使用DrawText()方法增加了计数器绘制窗口上绘图事件的数量。

wx.FocusEvent

焦点指示应用中当前选择的窗口小部件。 从键盘输入或从剪贴板粘贴的文本将发送到具有焦点的小部件。 有两种与焦点有关的事件类型。 wx.EVT_SET_FOCUS事件,当窗口小部件获得焦点时生成。 小部件失去焦点时,将生成wx.EVT_KILL_FOCUS。 通常,通过单击或使用键盘键来更改焦点,通常是选项卡Shift + 选项卡

focus_event.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we work with wx.FocusEvent.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class MyWindow(wx.Panel):

    def __init__(self, parent):
        super(MyWindow, self).__init__(parent)

        self.color = '#b3b3b3'

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
        self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen(self.color))
        x, y = self.GetSize()
        dc.DrawRectangle(0, 0, x, y)

    def OnSize(self, e):

        self.Refresh()

    def OnSetFocus(self, e):

        self.color = '#ff0000'
        self.Refresh()

    def OnKillFocus(self, e):

        self.color = '#b3b3b3'
        self.Refresh()

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        grid = wx.GridSizer(2, 2, 10, 10)
        grid.AddMany([(MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.LEFT, 9),
            (MyWindow(self), 0, wx.EXPAND|wx.TOP|wx.RIGHT, 9),
            (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.LEFT, 9),
            (MyWindow(self), 0, wx.EXPAND|wx.BOTTOM|wx.RIGHT, 9)])

        self.SetSizer(grid)

        self.SetSize((350, 250))
        self.SetTitle('Focus event')
        self.Centre()

    def OnMove(self, e):

        print(e.GetEventObject())
        x, y = e.GetPosition()
        self.st1.SetLabel(str(x))
        self.st2.SetLabel(str(y))

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们有四个面板。 重点突出显示的面板。

self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus)
self.Bind(wx.EVT_KILL_FOCUS, self.OnKillFocus)

我们将两个焦点事件绑定到事件处理器。

def OnPaint(self, e):

    dc = wx.PaintDC(self)

    dc.SetPen(wx.Pen(self.color))
    x, y = self.GetSize()
    dc.DrawRectangle(0, 0, x, y)

OnPaint()方法中,我们在窗口上绘制。 轮廓线的颜色取决于窗口是否具有焦点。 聚焦窗口的轮廓以红色绘制。

def OnSetFocus(self, e):

    self.color = '#ff0000'
    self.Refresh()

OnSetFocus()方法中,我们将self.color变量设置为红色。 我们刷新框架窗口,这将为其所有子窗口小部件生成一个绘制事件。 重新绘制了窗口,焦点所在的窗口的轮廓线有了新的颜色。

def OnKillFocus(self, e):

    self.color = '#b3b3b3'
    self.Refresh()

当窗口失去焦点时,将调用OnKillFocus()方法。 我们更改颜色值并刷新。

Focus event

图:焦点事件

wx.KeyEvent

当我们按下键盘上的一个键时,会生成一个wx.KeyEvent。 此事件发送到当前具有焦点的窗口小部件。 共有三种不同的按键处理器:

  • wx.EVT_KEY_DOWN
  • wx.EVT_KEY_UP
  • wx.EVT_CHAR

常见的请求是在按下 Esc 键时关闭应用。

key_event.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we work with wx.KeyEvent.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        pnl.SetFocus()

        self.SetSize((350, 250))
        self.SetTitle('Key event')
        self.Centre()

    def OnKeyDown(self, e):

        key = e.GetKeyCode()

        if key == wx.WXK_ESCAPE:

            ret  = wx.MessageBox('Are you sure to quit?', 'Question',
                wx.YES_NO | wx.NO_DEFAULT, self)

            if ret == wx.YES:
                self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,我们处理 Esc 按键。 显示一个消息框,以确认应用的终止。

pnl.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)

我们将事件处理器绑定到wx.EVT_KEY_DOWN事件。

key = e.GetKeyCode()

在这里,我们获得了按下的键的键控代码。

if key == wx.WXK_ESCAPE:

我们检查按键。 Esc 键具有wx.WXK_ESCAPE代码。

在本章中,我们讨论了 wxPython 中的事件。

wxPython 对话框

原文: http://zetcode.com/wxpython/dialogs/

对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。

一个简单的消息框

一个消息框向用户提供简短信息。 一个很好的例子是 CD 刻录应用。 CD 刻录完成后,将弹出一个消息框。

message_box.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example shows a simple
message box.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        wx.CallLater(3000, self.ShowMessage)

        self.SetSize((300, 200))
        self.SetTitle('Message box')
        self.Centre()

    def ShowMessage(self):
        wx.MessageBox('Download completed', 'Info',
            wx.OK | wx.ICON_INFORMATION)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

此示例显示三秒钟后的消息框。

wx.CallLater(3000, self.ShowMessage)

wx.CallLater在三秒钟后调用一个方法。 第一个参数是时间值,之后将调用给定方法。 该参数以毫秒为单位。 第二个参数是要调用的方法。

def ShowMessage(self):
    wx.MessageBox('Download completed', 'Info', 
        wx.OK | wx.ICON_INFORMATION)

wx.MessageBox显示一个小的对话框窗口。 我们提供了三个参数:文本消息,标题消息和标志。 这些标志用于显示不同的按钮和图标。 在本例中,我们显示一个确定按钮和信息图标。

Message box

图:一个消息框

预定义对话框

wxPython 有几个预定义的对话框。 这些是用于常见编程任务的对话框,例如显示文本,接收输入,加载和保存文件。

MessageDialog

消息对话框用于向用户显示消息。 它们比我们在前面的示例中看到的简单消息框更加灵活。 它们是可定制的。 我们可以更改将在对话框中显示的图标和按钮。

标志 含义
wx.OK 显示确定按钮
wx.CANCEL 显示取消按钮
wx.YES_NO 显示是,否按钮
wx.YES_DEFAULT 将是按钮设为默认
wx.NO_DEFAULT 将无按钮设为默认
wx.ICON_EXCLAMATION 显示警报图标
wx.ICON_ERROR 显示错误图标
wx.ICON_HAND wx.ICON_ERROR相同
wx.ICON_INFORMATION 显示信息图标
wx.ICON_QUESTION 显示问题图标

这些是可以与wx.MessageDialog类一起使用的标志。

message_dialogs.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This example shows four types of
message dialogs.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        panel = wx.Panel(self)

        hbox = wx.BoxSizer()
        sizer = wx.GridSizer(2, 2, 2, 2)

        btn1 = wx.Button(panel, label='Info')
        btn2 = wx.Button(panel, label='Error')
        btn3 = wx.Button(panel, label='Question')
        btn4 = wx.Button(panel, label='Alert')

        sizer.AddMany([btn1, btn2, btn3, btn4])

        hbox.Add(sizer, 0, wx.ALL, 15)
        panel.SetSizer(hbox)

        btn1.Bind(wx.EVT_BUTTON, self.ShowMessage1)
        btn2.Bind(wx.EVT_BUTTON, self.ShowMessage2)
        btn3.Bind(wx.EVT_BUTTON, self.ShowMessage3)
        btn4.Bind(wx.EVT_BUTTON, self.ShowMessage4)

        self.SetSize((300, 200))
        self.SetTitle('Messages')
        self.Centre()

    def ShowMessage1(self, event):
        dial = wx.MessageDialog(None, 'Download completed', 'Info', wx.OK)
        dial.ShowModal()

    def ShowMessage2(self, event):
        dial = wx.MessageDialog(None, 'Error loading file', 'Error',
            wx.OK | wx.ICON_ERROR)
        dial.ShowModal()

    def ShowMessage3(self, event):
        dial = wx.MessageDialog(None, 'Are you sure to quit?', 'Question',
            wx.YES_NO | wx.NO_DEFAULT | wx.ICON_QUESTION)
        dial.ShowModal()

    def ShowMessage4(self, event):
        dial = wx.MessageDialog(None, 'Unallowed operation', 'Exclamation',
            wx.OK | wx.ICON_EXCLAMATION)
        dial.ShowModal()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们创建了四个按钮并将它们放入网格大小调整器中。 这些按钮将显示四个不同的对话框窗口。 我们通过指定不同的样式标志来创建它们。

def ShowMessage2(self, event):
    dial = wx.MessageDialog(None, 'Error loading file', 'Error', 
        wx.OK | wx.ICON_ERROR)
    dial.ShowModal()

消息对话框的创建很简单。 通过将None作为父级,将对话框设置为顶级窗口。 这两个字符串提供了消息文本和对话框标题。 通过指定wx.OKwx.ICON_ERROR标志,我们显示一个 OK 按钮和一个错误图标。 为了在屏幕上显示对话框,我们调用ShowModal()方法。

AboutDialog

几乎每个应用都有一个典型的“关于”对话框。 通常将其放在“帮助”菜单中。 该对话框的目的是向用户提供有关应用名称和版本的基本信息。 过去,这些对话框非常简短。 如今,这些框中的大多数都提供了有关作者的其他信息。 他们感谢其他程序员或文档编写者。 他们还提供有关应用许可证的信息。 这些框可以显示公司徽标或应用徽标。

为了创建一个关于对话框,我们必须创建两个对象。 一个wx.adv.AboutDialogInfo和一个wx.adv.AboutBox

wxPython 可以显示两种“关于”框。 这取决于我们使用的平台和调用的方法。 它可以是本机对话框,也可以是 wxPython 通用对话框。 Windows 本机“关于”对话框无法显示自定义图标,许可证文本或 URL。 如果我们省略这三个字段,则 wxPython 将显示一个本机对话框。 否则,它将诉诸通用。 如果我们想保持本地化,建议在单独的菜单项中提供许可证信息。 GTK+ 可以显示所有这些字段。

about_dialog.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
ZetCode wxPython tutorial

In this example, we create an
about dialog box.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
'''

import wx
import wx.adv

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        menubar = wx.MenuBar()
        help = wx.Menu()
        help.Append(wx.ID_ANY, '&About')
        help.Bind(wx.EVT_MENU, self.OnAboutBox)

        menubar.Append(help, '&Help')
        self.SetMenuBar(menubar)

        self.SetSize((350, 250))
        self.SetTitle('About dialog box')
        self.Centre()

    def OnAboutBox(self, e):

        description = """File Hunter is an advanced file manager for
the Unix operating system. Features include powerful built-in editor,
advanced search capabilities, powerful batch renaming, file comparison,
extensive archive handling and more.
"""

        licence = """File Hunter is free software; you can redistribute
it and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

File Hunter is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details. You should have
received a copy of the GNU General Public License along with File Hunter;
if not, write to the Free Software Foundation, Inc., 59 Temple Place,
Suite 330, Boston, MA  02111-1307  USA"""

        info = wx.adv.AboutDialogInfo()

        info.SetIcon(wx.Icon('hunter.png', wx.BITMAP_TYPE_PNG))
        info.SetName('File Hunter')
        info.SetVersion('1.0')
        info.SetDescription(description)
        info.SetCopyright('(C) 2007 - 2019 Jan Bodnar')
        info.SetWebSite('http://www.zetcode.com')
        info.SetLicence(licence)
        info.AddDeveloper('Jan Bodnar')
        info.AddDocWriter('Jan Bodnar')
        info.AddArtist('The Tango crew')
        info.AddTranslator('Jan Bodnar')

        wx.adv.AboutBox(info)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该示例有一个关于菜单项。 选择项目后,将显示关于框。

        description = """File Hunter is an advanced file manager for 
the Unix operating system. Features include powerful built-in editor, 
advanced search capabilities, powerful batch renaming, file comparison, 
extensive archive handling and more.
"""

最好不要在应用的代码中放入太多文本。 我们不想使示例过于复杂,因此我们将所有文本放入代码中。 但是在实际程序中,文本应单独放置在文件中。 它有助于我们维护应用。 例如,如果我们想将我们的应用翻译成其他语言。

info = wx.adv.AboutDialogInfo()

首先要做的是创建一个wx.AboutDialogInfo对象。 构造器为空。 它不接受任何参数。

info.SetIcon(wx.Icon('hunter.png', wx.BITMAP_TYPE_PNG))
info.SetName('File Hunter')
info.SetVersion('1.0')
info.SetDescription(description)
info.SetCopyright('(C) 2007 - 2014 Jan Bodnar')
info.SetWebSite('http://www.zetcode.com')
info.SetLicence(licence)
info.AddDeveloper('Jan Bodnar')
info.AddDocWriter('Jan Bodnar')
info.AddArtist('The Tango crew')
info.AddTranslator('Jan Bodnar')

下一步是在创建的wx.AboutDialogInfo对象上调用所有必需的方法。

wx.adv.AboutBox(info)

最后,我们创建一个wx.adv.AboutBox小部件。 它唯一需要的参数是wx.adv.AboutDialogInfo对象。

About dialog box

图:关于对话框

自定义对话框

在下一个示例中,我们创建一个自定义对话框。 图像编辑应用可以更改图片的颜色深度。 为了提供这种功能,我们可以创建一个合适的对话框。

custom_dialog.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

'''
ZetCode wxPython tutorial

In this code example, we create a
custom dialog.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
'''

import wx

class ChangeDepthDialog(wx.Dialog):

    def __init__(self, *args, **kw):
        super(ChangeDepthDialog, self).__init__(*args, **kw)

        self.InitUI()
        self.SetSize((250, 200))
        self.SetTitle("Change Color Depth")

    def InitUI(self):

        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)

        sb = wx.StaticBox(pnl, label='Colors')
        sbs = wx.StaticBoxSizer(sb, orient=wx.VERTICAL)
        sbs.Add(wx.RadioButton(pnl, label='256 Colors',
            style=wx.RB_GROUP))
        sbs.Add(wx.RadioButton(pnl, label='16 Colors'))
        sbs.Add(wx.RadioButton(pnl, label='2 Colors'))

        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox1.Add(wx.RadioButton(pnl, label='Custom'))
        hbox1.Add(wx.TextCtrl(pnl), flag=wx.LEFT, border=5)
        sbs.Add(hbox1)

        pnl.SetSizer(sbs)

        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        okButton = wx.Button(self, label='Ok')
        closeButton = wx.Button(self, label='Close')
        hbox2.Add(okButton)
        hbox2.Add(closeButton, flag=wx.LEFT, border=5)

        vbox.Add(pnl, proportion=1,
            flag=wx.ALL|wx.EXPAND, border=5)
        vbox.Add(hbox2, flag=wx.ALIGN_CENTER|wx.TOP|wx.BOTTOM, border=10)

        self.SetSizer(vbox)

        okButton.Bind(wx.EVT_BUTTON, self.OnClose)
        closeButton.Bind(wx.EVT_BUTTON, self.OnClose)

    def OnClose(self, e):

        self.Destroy()

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        tb = self.CreateToolBar()
        tb.AddTool(toolId=wx.ID_ANY, label='', bitmap=wx.Bitmap('color.png'))

        tb.Realize()

        tb.Bind(wx.EVT_TOOL, self.OnChangeDepth)

        self.SetSize((350, 250))
        self.SetTitle('Custom dialog')
        self.Centre()

    def OnChangeDepth(self, e):

        cdDialog = ChangeDepthDialog(None,
            title='Change Color Depth')
        cdDialog.ShowModal()
        cdDialog.Destroy()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的示例中,我们创建了一个自定义对话框。

class ChangeDepthDialog(wx.Dialog):

    def __init__(self, *args, **kw):
        super(ChangeDepthDialog, self).__init__(*args, **kw) 

在我们的代码示例中,我们创建了一个自定义的ChangeDepthDialog对话框。 我们继承自wx.Dialog小部件。

def OnChangeDepth(self, e):

    cdDialog = ChangeDepthDialog(None,
        title='Change Color Depth')
    cdDialog.ShowModal()
    cdDialog.Destroy()

我们实例化一个ChangeDepthDialog class。 然后我们调用ShowModal()方法。 稍后,我们必须使用Destroy()销毁对话框。请注意,对话框和顶层窗口之间的视觉差异。 下图的对话框已激活。 在对话框被销毁之前,我们无法使用顶层窗口。 窗口的标题栏有明显的区别。

Custom dialog

图:一个自定义对话框

在本章中,我们介绍了对话框。

小部件

原文: http://zetcode.com/wxpython/widgets/

在本节中,我们将介绍 wxPython 中的基本小部件。 每个小部件都有一个小的代码示例。 小部件是应用的基本构建块。 wxPython 有各种各样的小部件,包括按钮,复选框,滑块和列表框。

  • wx.Button
  • wx.ToggleButton
  • wx.StaticText
  • wx.StaticLine
  • wx.StaticBox
  • wx.ComboBox
  • wx.CheckBox
  • wx.StatusBar
  • wx.RadioButton
  • wx.Gauge
  • wx.Slider
  • wx.SpinCtrl

wx.Button

wx.Button是一个简单的小部件。 它包含一个文本字符串。 用于触发动作。

button_wid.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this code example, we create a
button widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)
        closeButton = wx.Button(pnl, label='Close', pos=(20, 20))

        closeButton.Bind(wx.EVT_BUTTON, self.OnClose)

        self.SetSize((350, 250))
        self.SetTitle('wx.Button')
        self.Centre()

    def OnClose(self, e):

        self.Close(True)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()  

在代码示例中,我们创建一个“关闭”按钮,当按下该按钮时,它将终止应用。

cbtn = wx.Button(pnl, label='Close', pos=(20, 20))

wx.Button小部件已创建。 在小部件的构造器中,我们提供按钮的标签和面板上的位置。

cbtn.Bind(wx.EVT_BUTTON, self.OnClose)

当我们单击按钮时,将触发wx.EVT_BUTTON事件。 我们为事件指定事件处理器。

def OnClose(self, e):

    self.Close(True)   

OnClose()方法中,我们使用Close()方法终止应用。

wx.Button

图:wx.Button

wx.ToggleButton

wx.ToggleButton是具有两种状态的按钮:已按下和未按下。 通过单击可以在这两种状态之间切换。 在某些情况下此功能非常合适。

toggle_buttons.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this code example, we create three
toggle button widgets.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)

        self.col = wx.Colour(0, 0, 0)

        rtb = wx.ToggleButton(pnl, label='red', pos=(20, 25))
        gtb = wx.ToggleButton(pnl, label='green', pos=(20, 60))
        btb = wx.ToggleButton(pnl, label='blue', pos=(20, 100))

        self.cpnl  = wx.Panel(pnl, pos=(150, 20), size=(110, 110))
        self.cpnl.SetBackgroundColour(self.col)

        rtb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleRed)
        gtb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleGreen)
        btb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleBlue)

        self.SetSize((350, 250))
        self.SetTitle('Toggle buttons')
        self.Centre()

    def ToggleRed(self, e):

        obj = e.GetEventObject()
        isPressed = obj.GetValue()

        green = self.col.Green()
        blue = self.col.Blue()

        if isPressed:
            self.col.Set(255, green, blue)
        else:
            self.col.Set(0, green, blue)

        self.cpnl.SetBackgroundColour(self.col)
        self.cpnl.Refresh()

    def ToggleGreen(self, e):

        obj = e.GetEventObject()
        isPressed = obj.GetValue()

        red = self.col.Red()
        blue = self.col.Blue()

        if isPressed:
            self.col.Set(red, 255, blue)
        else:
            self.col.Set(red, 0, blue)

        self.cpnl.SetBackgroundColour(self.col)
        self.cpnl.Refresh()

    def ToggleBlue(self, e):

        obj = e.GetEventObject()
        isPressed = obj.GetValue()

        red = self.col.Red()
        green = self.col.Green()

        if isPressed:
            self.col.Set(red, green, 255)
        else:
            self.col.Set(red, green, 0)

        self.cpnl.SetBackgroundColour(self.col)
        self.cpnl.Refresh()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们有红色,绿色和蓝色的切换按钮和一个面板。 我们通过单击切换按钮来更改面板的颜色。

rtb = wx.ToggleButton(pnl, label='red', pos=(20, 25)) 

wx.ToggleButton小部件已创建。

self.cpnl  = wx.Panel(pnl, pos=(150, 20), size=(110, 110))
self.cpnl.SetBackgroundColour(self.col) 

这是一个面板的颜色,我们将使用切换按钮对其进行修改。

rtb.Bind(wx.EVT_TOGGLEBUTTON, self.ToggleRed) 

当我们单击rtb切换按钮时,将调用ToggleRed()事件处理器。

def ToggleRed(self, e):

    obj = e.GetEventObject()
    isPressed = obj.GetValue()

    green = self.col.Green()
    blue = self.col.Blue()

    if isPressed:
        self.col.Set(255, green, blue)
    else:
        self.col.Set(0, green, blue)

    self.cpnl.SetBackgroundColour(self.col)

ToggleRed()方法中,我们对按下rtb按钮这一事实作出反应。 我们找出颜色部分并更新颜色面板的颜色。

Toggle buttons

图:开关按钮

wx.StaticText

wx.StaticText小部件显示一行或多行只读文本。

static_text.py

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this code example, we create a static text.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        txt1 = '''I'm giving up the ghost of love
in the shadows cast on devotion
She is the one that I adore
creed of my silent suffocation
Break this bittersweet spell on me
lost in the arms of destiny'''

        txt2 = '''There is something in the way
You're always somewhere else
Feelings have deserted me
To a point of no return
I don't believe in God
But I pray for you'''

        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)

        font = wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.DEFAULT)

        st1 = wx.StaticText(pnl, label=txt1, style=wx.ALIGN_LEFT)
        st2 = wx.StaticText(pnl, label=txt2, style=wx.ALIGN_LEFT)

        st1.SetFont(font)
        st2.SetFont(font)

        vbox.Add(st1, flag=wx.ALL, border=15)
        vbox.Add(st2, flag=wx.ALL, border=15)

        pnl.SetSizer(vbox)

        self.SetTitle('Bittersweet')
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在示例中,我们使用wx.StaticText小部件显示了两首 Bittersweet 歌曲的节奏。

font = wx.Font(13, wx.DEFAULT, wx.NORMAL, wx.DEFAULT)

我们为文本创建一个字体对象。

        txt1 = '''I'm giving up the ghost of love
in the shadows cast on devotion
She is the one that I adore
creed of my silent suffocation
Break this bittersweet spell on me
lost in the arms of destiny'''

这是要在wx.StaticText小部件中显示的字符串。

st1 = wx.StaticText(pnl, label=txt1, style=wx.ALIGN_LEFT)

我们创建wx.StaticText小部件。 文本将向左对齐。

st1.SetFont(font)
st2.SetFont(font)

我们使用SetFont()将字体设置为静态文本小部件。

wx.StaticText

图:wx.StaticText

wx.StaticLine

此小部件在窗口上显示一条简单的线。 它可以是水平或垂直的。

static_line.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this code example, we create a static line.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)

        font = wx.Font(10, wx.DEFAULT, wx.NORMAL, wx.BOLD)
        heading = wx.StaticText(self, label='The Central Europe',
                                pos=(25, 15), size=(200, -1))
        heading.SetFont(font)

        wx.StaticLine(self, pos=(25, 50), size=(300,1))

        wx.StaticText(self, label='Slovakia', pos=(25, 80))
        wx.StaticText(self, label='Hungary', pos=(25, 100))
        wx.StaticText(self, label='Poland', pos=(25, 120))
        wx.StaticText(self, label='Czech Republic', pos=(25, 140))
        wx.StaticText(self, label='Germany', pos=(25, 160))
        wx.StaticText(self, label='Slovenia', pos=(25, 180))
        wx.StaticText(self, label='Austria', pos=(25, 200))
        wx.StaticText(self, label='Switzerland', pos=(25, 220))

        wx.StaticText(self, label='5 445 000', pos=(250, 80))
        wx.StaticText(self, label='10 014 000', pos=(250, 100))
        wx.StaticText(self, label='38 186 000', pos=(250, 120))
        wx.StaticText(self, label='10 562 000', pos=(250, 140))
        wx.StaticText(self, label='81 799 000', pos=(250, 160))
        wx.StaticText(self, label='2 050 000', pos=(250, 180))
        wx.StaticText(self, label='8 414 000', pos=(250, 200))
        wx.StaticText(self, label='7 866 000', pos=(250, 220))

        wx.StaticLine(self, pos=(25, 260), size=(300,1))

        tsum = wx.StaticText(self, label='164 336 000', pos=(240, 280))
        sum_font = tsum.GetFont()
        sum_font.SetWeight(wx.BOLD)
        tsum.SetFont(sum_font)

        btn = wx.Button(self, label='Close', pos=(140, 310))

        btn.Bind(wx.EVT_BUTTON, self.OnClose)

        self.SetSize((360, 380))
        self.SetTitle('wx.StaticLine')
        self.Centre()

    def OnClose(self, e):

        self.Close(True)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该脚本显示了中欧国家及其人口。 wx.StatLine使它看起来更具视觉吸引力。

wx.StaticLine(self, pos=(25, 50), size=(300,1))

这是wx.StaticLine的构造器

wx.StaticLine

图:wx.StaticLine

wx.StaticBox

这是一种装饰器小部件。 它用于对各种小部件进行逻辑分组。 请注意,必须在其包含的窗口小部件之前创建此窗口小部件,并且这些窗口小部件应该是静态框的同级,而不是子级。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 

        self.InitUI()

    def InitUI(self):   

        pnl = wx.Panel(self)

        wx.StaticBox(pnl, label='Personal Info', pos=(5, 5), size=(240, 170))
        wx.CheckBox(pnl, label='Male', pos=(15, 30))
        wx.CheckBox(pnl, label='Married', pos=(15, 55))
        wx.StaticText(pnl, label='Age', pos=(15, 95))
        wx.SpinCtrl(pnl, value='1', pos=(55, 90), size=(60, -1), min=1, max=120)

        btn = wx.Button(pnl, label='Ok', pos=(90, 185), size=(60, -1))

        btn.Bind(wx.EVT_BUTTON, self.OnClose)

        self.SetSize((270, 250))
        self.SetTitle('Static box')
        self.Centre()
        self.Show(True)          

    def OnClose(self, e):

        self.Close(True)    

def main():

    ex = wx.App()
    Example(None)
    ex.MainLoop()    

if __name__ == '__main__':
    main()   

我们有一个wx.StaticBox来装饰其他四个小部件。

Static box

图:静态框

wx.ComboBox

wx.ComboBox是单行文本字段,带有向下箭头图像的按钮和列表框的组合。 当您按下按钮时,将出现一个列表框。 用户只能从提供的字符串列表中选择一个选项。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 

        self.InitUI()

    def InitUI(self):   

        pnl = wx.Panel(self)

        distros = ['Ubuntu', 'Arch', 'Fedora', 'Debian', 'Mint']
        cb = wx.ComboBox(pnl, pos=(50, 30), choices=distros, 
            style=wx.CB_READONLY)

        self.st = wx.StaticText(pnl, label='', pos=(50, 140))
        cb.Bind(wx.EVT_COMBOBOX, self.OnSelect)

        self.SetSize((250, 230))
        self.SetTitle('wx.ComboBox')
        self.Centre()
        self.Show(True)          

    def OnSelect(self, e):

        i = e.GetString()
        self.st.SetLabel(i)

def main():

    ex = wx.App()
    Example(None)
    ex.MainLoop()    

if __name__ == '__main__':
    main()   

从组合框中选择的选项显示在下面的标签中。

distros = ['Ubuntu', 'Arch', 'Fedora', 'Debian', 'Mint']

组合框将包含此字符串列表。

cb = wx.ComboBox(pnl, pos=(50, 30), choices=distros, 
            style=wx.CB_READONLY)

wx.ComboBox小部件已创建。 choices参数采用要在组合框显示的字符串列表。 wx.CB_READONLY样式使列表的字符串为只读。

cb.Bind(wx.EVT_COMBOBOX, self.OnSelect)

当我们从组合框中选择一个选项时,将触发wx.EVT_COMBOBOX事件。 我们将OnSelect()事件处理器插入此事件。

def OnSelect(self, e):

    i = e.GetString()
    self.st.SetLabel(i)

我们从组合框中获取选定的项目并将其设置为标签。

wx.ComboBox

图:wx.ComboBox

wx.CheckBox

wx.CheckBox是具有两种状态的窗口小部件:开和关。 这是一个带有标签的盒子。 标签可以设置在框的右侧或左侧。 如果选中wx.CheckBox,则在方框中用勾号表示。

checkbox.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create a checkbox widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)

        vbox = wx.BoxSizer(wx.HORIZONTAL)

        cb = wx.CheckBox(pnl, label='Show title')
        cb.SetValue(True)
        cb.Bind(wx.EVT_CHECKBOX, self.ShowOrHideTitle)

        vbox.Add(cb, flag=wx.TOP|wx.LEFT, border=30)

        pnl.SetSizer(vbox)

        self.SetTitle('wx.CheckBox')
        self.Centre()

    def ShowOrHideTitle(self, e):

        sender = e.GetEventObject()
        isChecked = sender.GetValue()

        if isChecked:
            self.SetTitle('wx.CheckBox')
        else:
            self.SetTitle('')

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在上面的示例中,我们使用wx.CheckBox小部件显示或隐藏窗口标题。

cb = wx.CheckBox(pnl, label='Show title')

这是wx.CheckBox小部件的构造器。

cb.SetValue(True)

默认情况下显示框架窗口的标题,因此我们使用SetValue()方法检查wx.CheckBox小部件。

cb.Bind(wx.EVT_CHECKBOX, self.ShowOrHideTitle)

当我们单击wx.CheckBox小部件时,将触发wx.EVT_CHECKBOX事件。 在此事件上调用ShowOrHideTitle()事件处理器。

def ShowOrHideTitle(self, e):

    sender = e.GetEventObject()
    isChecked = sender.GetValue()

    if isChecked:
        self.SetTitle('wx.CheckBox')            
    else: 
        self.SetTitle('')   

ShowOrHideTitle()方法中,我们根据wx.CheckBox小部件的状态显示或隐藏标题。

wx.CheckBox

图:wx.CheckBox

wx.StatusBar

wx.StatusBar小部件用于显示应用状态信息。 它可以分为几个部分以显示不同类型的信息。 我们可以在wx.StatusBar中插入其他小部件。 它可以用作对话框的替代方法,因为对话框经常被滥用,并且大多数用户不喜欢它们。 我们可以通过两种方式创建wx.StatusBar。 我们可以手动创建自己的wx.StatusBar并调用SetStatusBar()方法,也可以简单地调用CreateStatusBar()方法。 后一种方法为我们创建了默认的wx.StatusBar

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 

        self.InitUI()

    def InitUI(self):   

        pnl = wx.Panel(self)

        button = wx.Button(pnl, label='Button', pos=(20, 20))
        text = wx.CheckBox(pnl, label='CheckBox', pos=(20, 90))
        combo = wx.ComboBox(pnl, pos=(120, 22), choices=['Python', 'Ruby'])
        slider = wx.Slider(pnl, 5, 6, 1, 10, (120, 90), (110, -1))        

        pnl.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter)
        button.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter)
        text.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter)
        combo.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter)
        slider.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter)

        self.sb = self.CreateStatusBar()

        self.SetSize((250, 230))
        self.SetTitle('wx.Statusbar')
        self.Centre()
        self.Show(True)     

    def OnWidgetEnter(self, e):

        name = e.GetEventObject().GetClassName()
        self.sb.SetStatusText(name + ' widget')
        e.Skip()               

def main():

    ex = wx.App()
    Example(None)
    ex.MainLoop()    

if __name__ == '__main__':
    main()   

在我们的示例中,我们有一个wx.Frame小部件以及其他五个小部件。 如果将鼠标指针悬停在窗口小部件上,则其名称将显示在wx.StatusBar中。

pnl.Bind(wx.EVT_ENTER_WINDOW, self.OnWidgetEnter)

如果我们输入小部件的区域,则会生成EVT_ENTER_WINDOW事件。

self.sb = self.CreateStatusBar()

使用CreateStatusBar()方法创建状态栏。

def OnWidgetEnter(self, e):

    name = e.GetEventObject().GetClassName()
    self.sb.SetStatusText(name + ' widget')
    e.Skip()  

OnWidgetEnter()方法内部,我们找出使用鼠标指针输入的小部件的名称。 我们使用SetStatusText()方法设置状态文本。

StatusBar

图:wx.StatusBar

wx.RadioButton

wx.RadioButton是一个小部件,允许用户从一组选项中选择一个唯一选项。 通过使组中的第一个单选按钮包含wx.RB_GROUP样式来定义单选按钮组。 在第一个带有此样式标记的单选按钮之后定义的所有其他单选按钮将添加到第一个单选按钮的功能组中。 用wx.RB_GROUP标志声明另一个单选按钮将启动一个新的单选按钮组。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw) 

        self.InitUI()

    def InitUI(self):   

        pnl = wx.Panel(self)

        self.rb1 = wx.RadioButton(pnl, label='Value A', pos=(10, 10), 
            style=wx.RB_GROUP)
        self.rb2 = wx.RadioButton(pnl, label='Value B', pos=(10, 30))
        self.rb3 = wx.RadioButton(pnl, label='Value C', pos=(10, 50))

        self.rb1.Bind(wx.EVT_RADIOBUTTON, self.SetVal)
        self.rb2.Bind(wx.EVT_RADIOBUTTON, self.SetVal)
        self.rb3.Bind(wx.EVT_RADIOBUTTON, self.SetVal)

        self.sb = self.CreateStatusBar(3)

        self.sb.SetStatusText("True", 0)
        self.sb.SetStatusText("False", 1)
        self.sb.SetStatusText("False", 2)   

        self.SetSize((210, 210))
        self.SetTitle('wx.RadioButton')
        self.Centre()
        self.Show(True)     

    def SetVal(self, e):

        state1 = str(self.rb1.GetValue())
        state2 = str(self.rb2.GetValue())
        state3 = str(self.rb3.GetValue())

        self.sb.SetStatusText(state1, 0)
        self.sb.SetStatusText(state2, 1)
        self.sb.SetStatusText(state3, 2)            

def main():

    ex = wx.App()
    Example(None)
    ex.MainLoop()    

if __name__ == '__main__':
    main()   

我们有一组三个单选按钮。 每个单选按钮的状态显示在状态栏中。

self.rb1 = wx.RadioButton(pnl, label='Value A', pos=(10, 10), 
    style=wx.RB_GROUP)
self.rb2 = wx.RadioButton(pnl, label='Value B', pos=(10, 30))
self.rb3 = wx.RadioButton(pnl, label='Value C', pos=(10, 50))

我们创建三个单选按钮。 第一个单选按钮设置为wx.RB_GROUP样式。 它将启动一个新的广播组。

self.rb1.Bind(wx.EVT_RADIOBUTTON, self.SetVal)

我们将wx.EVT_RADIOBUTTON事件绑定到SetVal()事件处理器。

self.sb = self.CreateStatusBar(3)

self.sb.SetStatusText("True", 0)
self.sb.SetStatusText("False", 1)
self.sb.SetStatusText("False", 2) 

我们创建一个带有三个字段的状态栏。 我们将初始文本设置为与单选按钮状态相对应的状态栏。

def SetVal(self, e):

    state1 = str(self.rb1.GetValue())
    state2 = str(self.rb2.GetValue())
    state3 = str(self.rb3.GetValue())

    self.sb.SetStatusText(state1, 0)
    self.sb.SetStatusText(state2, 1)
    self.sb.SetStatusText(state3, 2)  

SetVal()方法内,我们找出单选按钮的状态。 我们将状态栏字段更新为当前的单选按钮值。

wx.RadioButton

图:wx.RadioButton

wx.Gauge

wx.Gauge是在处理冗长的任务时使用的小部件。 它具有一个指示器以显示任务的当前状态。

gauge_wid.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create gauge widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

TASK_RANGE = 50

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.timer = wx.Timer(self, 1)
        self.count = 0

        self.Bind(wx.EVT_TIMER, self.OnTimer, self.timer)

        pnl = wx.Panel(self)
        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)

        self.gauge = wx.Gauge(pnl, range=TASK_RANGE, size=(250, -1))
        self.btn1 = wx.Button(pnl, wx.ID_OK)
        self.btn2 = wx.Button(pnl, wx.ID_STOP)
        self.text = wx.StaticText(pnl, label='Task to be done')

        self.Bind(wx.EVT_BUTTON, self.OnOk, self.btn1)
        self.Bind(wx.EVT_BUTTON, self.OnStop, self.btn2)

        hbox1.Add(self.gauge, proportion=1, flag=wx.ALIGN_CENTRE)
        hbox2.Add(self.btn1, proportion=1, flag=wx.RIGHT, border=10)
        hbox2.Add(self.btn2, proportion=1)
        hbox3.Add(self.text, proportion=1)

        vbox.Add((0, 30))

        vbox.Add(hbox1, flag=wx.ALIGN_CENTRE)

        vbox.Add((0, 20))

        vbox.Add(hbox2, proportion=1, flag=wx.ALIGN_CENTRE)
        vbox.Add(hbox3, proportion=1, flag=wx.ALIGN_CENTRE)

        pnl.SetSizer(vbox)

        self.SetTitle('wx.Gauge')
        self.Centre()

    def OnOk(self, e):

        if self.count >= TASK_RANGE:
            return

        self.timer.Start(100)
        self.text.SetLabel('Task in Progress')

    def OnStop(self, e):

        if self.count == 0 or self.count >= TASK_RANGE or not self.timer.IsRunning():
            return

        self.timer.Stop()
        self.text.SetLabel('Task Interrupted')

    def OnTimer(self, e):

        self.count = self.count + 1
        self.gauge.SetValue(self.count)

        if self.count == TASK_RANGE:

            self.timer.Stop()
            self.text.SetLabel('Task Completed')

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()                

我们有一个压力表和两个按钮。 一个按钮启动压力表,另一按钮停止压力表。

self.timer = wx.Timer(self, 1)
self.count = 0

我们使用wx.Timer在特定间隔执行代码。 届时我们将更新量规。 count变量用于确定已完成任务的一部分。

self.gauge = wx.Gauge(pnl, range=TASK_RANGE, size=(250, -1))

这是wx.Gauge小部件的构造器。 range参数设置窗口小部件的最大整数值。

def OnOk(self, e):

    if self.count >= TASK_RANGE:
        return

    self.timer.Start(100)
    self.text.SetLabel('Task in Progress')

当我们单击确定按钮时,将调用OnOk()方法。 我们首先检查count变量是否在任务范围内。 如果没有,我们从方法中返回。 如果任务尚未完成,我们将启动计时器并更新静态文本。

def OnStop(self, e):

    if self.count == 0 or self.count >= TASK_RANGE or not self.timer.IsRunning():
        return

    self.timer.Stop()
    self.text.SetLabel('Task Interrupted')

当我们单击停止按钮时,将调用OnStop()方法。 我们检查停止任务的条件。 如果遇到他们,我们将停止计时器并更新静态文本。

def OnTimer(self, e):

    self.count = self.count + 1
    self.gauge.SetValue(self.count)

    if self.count == TASK_RANGE:

        self.timer.Stop()
        self.text.SetLabel('Task Completed')

启动计时器后,会定期调用OnTimer()方法。 在该方法中,我们更新了count变量和gauge控件。 如果count变量等于TASK_RANGE,我们将停止计时器并更新静态文本。

wx.Gauge

图:wx.Gauge

wx.Slider

wx.Slider是具有简单句柄的小部件。 该手柄可以前后拉动。 这样,我们可以选择特定任务。

slider_wid.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create slider control.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)

        sizer = wx.GridBagSizer(5, 5)

        sld = wx.Slider(pnl, value=200, minValue=150, maxValue=500,
                        style=wx.SL_HORIZONTAL)

        sld.Bind(wx.EVT_SCROLL, self.OnSliderScroll)
        sizer.Add(sld, pos=(0, 0), flag=wx.ALL|wx.EXPAND, border=25)

        self.txt = wx.StaticText(pnl, label='200')
        sizer.Add(self.txt, pos=(0, 1), flag=wx.TOP|wx.RIGHT, border=25)

        sizer.AddGrowableCol(0)
        pnl.SetSizer(sizer)

        self.SetTitle('wx.Slider')
        self.Centre()

    def OnSliderScroll(self, e):

        obj = e.GetEventObject()
        val = obj.GetValue()

        self.txt.SetLabel(str(val))

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()  

静态文本中显示了在滑块中选择的值。

sld = wx.Slider(pnl, value=200, minValue=150, maxValue=500,
                style=wx.SL_HORIZONTAL)

创建了wx.Slider。 我们使用值参数提供滑块的初始位置,并使用minValuemaxValue参数提供最小和最大滑块位置。 wx.SL_HORIZONTAL使滑块变为水平。

sld.Bind(wx.EVT_SCROLL, self.OnSliderScroll)

遇到wx.EVT_SCROLL事件时,将调用OnSliderScroll()方法。

self.txt = wx.StaticText(pnl, label='200')

当前选择的滑块值显示在静态文本中,该文本位于滑块下方。

def OnSliderScroll(self, e):

    obj = e.GetEventObject()
    val = obj.GetValue()

    self.txt.SetLabel(str(val)) 

OnSliderScroll()方法中,我们获取事件的发送者。 我们获取滑块的当前值并将其设置为静态文本。

wx.Slider

图:wx.Slider

wx.SpinCtrl

wx.SpinCtrl小部件使我们可以增加和减少值。

spin_ctrl.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example we create spin control.

author: Jan Bodnar
website: www.zetcode.com
last modified: April 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        pnl = wx.Panel(self)

        sizer = wx.GridBagSizer(5, 5)

        st1 = wx.StaticText(pnl, label='Convert Fahrenheit temperature to Celsius')
        sizer.Add(st1, pos=(0, 0), span=(1, 2), flag=wx.ALL, border=15)

        st2 = wx.StaticText(pnl, label='Fahrenheit:')
        sizer.Add(st2, pos=(1, 0), flag=wx.ALL | wx.ALIGN_CENTER, border=15)

        self.sc = wx.SpinCtrl(pnl, value='0')
        self.sc.SetRange(-459, 1000)

        sizer.Add(self.sc, pos=(1, 1), flag=wx.ALIGN_CENTER)

        st3 = wx.StaticText(pnl, label='Celsius:')
        sizer.Add(st3, pos=(2, 0), flag=wx.ALL|wx.ALIGN_RIGHT, border=15)

        self.celsius = wx.StaticText(pnl, label='')
        sizer.Add(self.celsius, pos=(2, 1), flag=wx.ALL, border=15)

        computeButton = wx.Button(pnl, label='Compute')
        computeButton.SetFocus()
        sizer.Add(computeButton, pos=(3, 0), flag=wx.ALIGN_RIGHT|wx.TOP, border=30)

        closeButton = wx.Button(pnl, label='Close')
        sizer.Add(closeButton, pos=(3, 1), flag=wx.ALIGN_LEFT|wx.TOP, border=30)

        computeButton.Bind(wx.EVT_BUTTON, self.OnCompute)
        closeButton.Bind(wx.EVT_BUTTON, self.OnClose)

        pnl.SetSizer(sizer)

        self.SetTitle('wx.SpinCtrl')
        self.Centre()

    def OnClose(self, e):

        self.Close(True)

    def OnCompute(self, e):

        fahr = self.sc.GetValue()
        cels = round((fahr - 32) * 5 / 9.0, 2)
        self.celsius.SetLabel(str(cels))

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()       

该脚本将华氏温度转换为摄氏温度。 我们使用wx.SpinCtrl小部件为华氏温度选择一个值。

self.sc = wx.SpinCtrl(pnl, value='0')
self.sc.SetRange(-459, 1000)

我们创建一个初始值为 0 的wx.SpinCtrl小部件。SetRange()设置该小部件的值范围。

def OnCompute(self, e):

    fahr = self.sc.GetValue()
    cels = round((fahr - 32) * 5 / 9.0, 2)
    self.celsius.SetLabel(str(cels)) 

当我们单击计算按钮时,将调用OnCompute()方法。 在方法的主体中,我们从旋转控件中获取当前值。 我们计算摄氏温度并将计算的温度设置为静态文本小部件。

wx.SpinCtrl

图:wx.SpinCtrl

wxPython 教程的这一部分专用于核心 wxPython 小部件。

wxPython 中的高级小部件

原文: http://zetcode.com/wxpython/advanced/

在本章中,我们讨论以下高级小部件:wx.ListBoxwx.html.HtmlWindowwx.ListCtrl

wxPython 有几个众所周知的高级小部件。 例如,树小部件,HTML 窗口,网格小部件,列表框小部件,列表小部件或具有高级样式功能的编辑器。

wx.ListBox小部件

wx.ListBox用于显示和使用项目列表。 可以在两种不同的状态下创建wx.ListBox:处于单选状态或多选状态。 单一选择状态是默认状态。

wx.ListBox中有两个重要事件。 第一个是wx.EVT_COMMAND_LISTBOX_SELECTED事件。 当我们在wx.ListBox中选择一个项目时,将生成此事件。 第二个事件是wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED事件。 当我们双击wx.ListBox中的项目时会生成该文件。 元素从零开始编号。 如果需要,滚动条会自动显示。

listbox.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a wx.ListBox widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        panel = wx.Panel(self)
        hbox = wx.BoxSizer(wx.HORIZONTAL)

        self.listbox = wx.ListBox(panel)
        hbox.Add(self.listbox, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)

        btnPanel = wx.Panel(panel)
        vbox = wx.BoxSizer(wx.VERTICAL)
        newBtn = wx.Button(btnPanel, wx.ID_ANY, 'New', size=(90, 30))
        renBtn = wx.Button(btnPanel, wx.ID_ANY, 'Rename', size=(90, 30))
        delBtn = wx.Button(btnPanel, wx.ID_ANY, 'Delete', size=(90, 30))
        clrBtn = wx.Button(btnPanel, wx.ID_ANY, 'Clear', size=(90, 30))

        self.Bind(wx.EVT_BUTTON, self.NewItem, id=newBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnRename, id=renBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnDelete, id=delBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnClear, id=clrBtn.GetId())
        self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename)

        vbox.Add((-1, 20))
        vbox.Add(newBtn)
        vbox.Add(renBtn, 0, wx.TOP, 5)
        vbox.Add(delBtn, 0, wx.TOP, 5)
        vbox.Add(clrBtn, 0, wx.TOP, 5)

        btnPanel.SetSizer(vbox)
        hbox.Add(btnPanel, 0.6, wx.EXPAND | wx.RIGHT, 20)
        panel.SetSizer(hbox)

        self.SetTitle('wx.ListBox')
        self.Centre()

    def NewItem(self, event):

        text = wx.GetTextFromUser('Enter a new item', 'Insert dialog')
        if text != '':
            self.listbox.Append(text)

    def OnRename(self, event):

        sel = self.listbox.GetSelection()
        text = self.listbox.GetString(sel)
        renamed = wx.GetTextFromUser('Rename item', 'Rename dialog', text)

        if renamed != '':
            self.listbox.Delete(sel)
            item_id = self.listbox.Insert(renamed, sel)
            self.listbox.SetSelection(item_id)

    def OnDelete(self, event):

        sel = self.listbox.GetSelection()
        if sel != -1:
            self.listbox.Delete(sel)

    def OnClear(self, event):
        self.listbox.Clear()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该示例显示了如何从wx.ListBox添加,修改和删除项目。

self.listbox = wx.ListBox(panel)
hbox.Add(self.listbox, wx.ID_ANY, wx.EXPAND | wx.ALL, 20)

我们创建一个空的wx.ListBox。 我们在列表框周围放置了 20px 的边框。

self.Bind(wx.EVT_LISTBOX_DCLICK, self.OnRename)

我们使用wx.EVT_LISTBOX_DCLICK事件绑定器将wx.EVT_COMMAND_LISTBOX_DOUBLE_CLICKED事件类型与OnRename()方法绑定。 这样,如果我们双击列表框中的特定元素,我们将显示一个重命名对话框。

def NewItem(self, event):

    text = wx.GetTextFromUser('Enter a new item', 'Insert dialog')
    if text != '':
        self.listbox.Append(text)        

我们通过单击“新建”按钮来调用NewItem()方法。 此方法使用包装器wx.GetTextFromUser()方法显示wx.TextEntryDialog。 我们输入的文本将返回到text变量。 如果文本不为空,则使用Append()方法将其附加到列表框。

if renamed != '':
    self.listbox.Delete(sel)
    item_id = self.listbox.Insert(renamed, sel)
    self.listbox.SetSelection(item_id)      

我们通过删除项目并在同一位置插入新项目来重命名该项目。 我们还将选择重新设置为修改后的项目。

def OnDelete(self, event):

    sel = self.listbox.GetSelection()
    if sel != -1:
        self.listbox.Delete(sel)

要删除项目,我们通过调用GetSelection()方法找到所选项目的索引。 然后,我们使用Delete()方法删除该项目。 Delete()方法的参数是所选索引。

def OnClear(self, event):
    self.listbox.Clear()

最简单的方法是清除整个列表框。 我们只需调用Clear()方法。

wx.ListBox widget

图:wx.ListBox小部件

wx.html.HtmlWindow小部件

wx.html.HtmlWindow小部件显示 HTML 页面。 它不是完整的浏览器。 我们可以使用wx.html.HtmlWindow小部件来做一些有趣的事情。

例如,在下面的程序中,我们创建一个显示基本统计信息的窗口。

page.html

<!DOCTYPE html>
<html>
<body bgcolor="#8e8e95">
  <table cellspacing="5" border="0" width="250">
    <tr width="200" align="left">
    <td bgcolor="#e7e7e7">&nbsp;&nbsp;Maximum</td>
    <td bgcolor="#aaaaaa">&nbsp;&nbsp;<b>9000</b></td>
    </tr>
    <tr align="left">
    <td bgcolor="#e7e7e7">&nbsp;&nbsp;Mean</td>
    <td bgcolor="#aaaaaa">&nbsp;&nbsp;<b>6076</b></td>
    </tr>
    <tr align="left">
    <td bgcolor="#e7e7e7">&nbsp;&nbsp;Minimum</td>
    <td bgcolor="#aaaaaa">&nbsp;&nbsp;<b>3800</b></td>
    </tr>
    <tr align="left">
    <td bgcolor="#e7e7e7">&nbsp;&nbsp;Median</td>
    <td bgcolor="#aaaaaa">&nbsp;&nbsp;<b>6000</b></td>
    </tr>
    <tr align="left">
    <td bgcolor="#e7e7e7">&nbsp;&nbsp;Standard Deviation</td>
    <td bgcolor="#aaaaaa">&nbsp;&nbsp;<b>6076</b></td>
    </tr>
  </table>
</body>
</html>

这是要显示的 HTML 页面。

htmlwin.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a wx.html.HtmlWindow widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx
import wx.html

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        panel = wx.Panel(self)

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)

        htmlwin = wx.html.HtmlWindow(panel, wx.ID_ANY, style=wx.NO_BORDER)
        htmlwin.SetStandardFonts()
        htmlwin.LoadPage("page.html")

        vbox.Add((-1, 10), 0)
        vbox.Add(htmlwin, 1, wx.EXPAND | wx.ALL, 9)

        bitmap = wx.StaticBitmap(panel, wx.ID_ANY, wx.Bitmap('newt.png'))
        hbox.Add(bitmap, 0, wx.LEFT | wx.BOTTOM | wx.TOP, 10)
        btnOk = wx.Button(panel, wx.ID_ANY, 'Ok')

        self.Bind(wx.EVT_BUTTON, self.OnClose, id=btnOk.GetId())

        hbox.Add((100, -1), 1, wx.EXPAND | wx.ALIGN_RIGHT)
        hbox.Add(btnOk, flag=wx.TOP | wx.BOTTOM | wx.RIGHT, border=10)
        vbox.Add(hbox, 0, wx.EXPAND)

        panel.SetSizer(vbox)

        self.SetTitle('Basic statistics')
        self.Centre()

    def OnClose(self, event):
        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该示例在wx.html.HtmlWindow小部件中分配 HTML 文件。

htmlwin = wx.html.HtmlWindow(panel, wx.ID_ANY, style=wx.NO_BORDER)
htmlwin.SetStandardFonts()
htmlwin.LoadPage("page.html")   

wx.html.HtmlWindow已创建。 HTML 文件使用LoadPage()方法加载。

图:wx.html.HtmlWindow示例

帮助窗口

我们可以使用wx.html.HtmlWindow在我们的应用中提供帮助。 我们可以创建一个独立的窗口,也可以创建将成为应用一部分的窗口。 以下脚本将使用后一种想法创建一个帮助窗口。

helpwindow.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a help window window
with wx.html.HtmlWindow.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx
import wx.html as html

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        toolbar = self.CreateToolBar()
        toolbar.AddTool(1, 'Exit', wx.Bitmap('exit.png'))
        toolbar.AddTool(2, 'Help', wx.Bitmap('help.png'))
        toolbar.Realize()

        self.splitter = wx.SplitterWindow(self)
        self.panelLeft = wx.Panel(self.splitter, wx.ID_ANY, style=wx.BORDER_SUNKEN)

        self.panelRight = wx.Panel(self.splitter)
        vbox2 = wx.BoxSizer(wx.VERTICAL)
        header = wx.Panel(self.panelRight, wx.ID_ANY)

        header.SetBackgroundColour('#6f6a59')
        header.SetForegroundColour('white')

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        st = wx.StaticText(header, wx.ID_ANY, 'Help')
        font = st.GetFont()
        font.SetFamily(wx.FONTFAMILY_ROMAN)
        font.SetPointSize(11)
        st.SetFont(font)

        hbox.Add(st, 1, wx.TOP | wx.BOTTOM | wx.LEFT, 8)

        closeBtn = wx.BitmapButton(header, wx.ID_ANY, wx.Bitmap('closebutton.png',
              wx.BITMAP_TYPE_PNG), style=wx.NO_BORDER)
        closeBtn.SetBackgroundColour('#6f6a59')

        hbox.Add(closeBtn, 0, wx.TOP|wx.BOTTOM, 8)
        header.SetSizer(hbox)

        vbox2.Add(header, 0, wx.EXPAND)

        helpWin = html.HtmlWindow(self.panelRight, style=wx.NO_BORDER)
        helpWin.LoadPage('help.html')

        vbox2.Add(helpWin, 1, wx.EXPAND)

        self.panelRight.SetSizer(vbox2)
        self.panelLeft.SetFocus()

        self.splitter.SplitVertically(self.panelLeft, self.panelRight)
        self.splitter.Unsplit()

        self.Bind(wx.EVT_BUTTON, self.CloseHelp, id=closeBtn.GetId())
        self.Bind(wx.EVT_TOOL, self.OnClose, id=1)
        self.Bind(wx.EVT_TOOL, self.OnHelp, id=2)

        self.panelLeft.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
        self.panelLeft.SetFocus()

        self.CreateStatusBar()

        self.SetTitle('Help')
        self.Centre()

    def OnClose(self, e):
        self.Close()

    def OnHelp(self, e):

        self.splitter.SplitVertically(self.panelLeft, self.panelRight)
        self.panelLeft.SetFocus()

    def CloseHelp(self, e):

        self.splitter.Unsplit()
        self.panelLeft.SetFocus()

    def OnKeyPressed(self, e):

        keycode = e.GetKeyCode()
        print(keycode)

        if keycode == wx.WXK_F1:

            self.splitter.SplitVertically(self.panelLeft, self.panelRight)
            self.panelLeft.SetFocus()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

帮助窗口一开始是隐藏的。 我们可以通过点击工具栏上的帮助按钮或按 F1 表现出来。 帮助窗口出现在应用的右侧。 要隐藏的帮助窗口,我们点击关闭按钮。

self.splitter.SplitVertically(self.panelLeft, self.panelRight)
self.splitter.Unsplit()

我们创建左面板和右面板并将其垂直拆分。 之后,我们调用Unsplit()方法。 默认情况下,该方法隐藏右侧或底部窗格。

我们将右侧面板分为两部分。 面板的标题和主体。 标头是已调整的wx.Panel。 标题由静态文本和位图按钮组成。 我们将wx.html.Window放入面板的主体中。

closeBtn = wx.BitmapButton(header, wx.ID_ANY, wx.Bitmap('closebutton.png',
      wx.BITMAP_TYPE_PNG), style=wx.NO_BORDER)
closeBtn.SetBackgroundColour('#6f6a59')

位图按钮样式设置为wx.NO_BORDER。 背景颜色设置为标题面板的颜色。 这样做是为了使按钮显示为标题的一部分。

helpWin = html.HtmlWindow(self.panelRight, style=wx.NO_BORDER)
helpWin.LoadPage('help.html')

我们在右侧面板上创建一个wx.html.HtmlWindow小部件。 我们的 HTML 代码位于单独的文件中。 这次我们调用LoadPage()方法来获取 HTML 代码。

self.panelLeft.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
self.panelLeft.SetFocus()

我们将重点放在左侧面板上。 我们可以使用F1键启动帮助窗口。 为了使用键盘控制窗口,必须具有焦点。 如果未设置焦点,则必须首先单击面板,然后才能通过按 F1 键启动帮助窗口。

def OnHelp(self, e):

    self.splitter.SplitVertically(self.panelLeft, self.panelRight)
    self.panelLeft.SetFocus()

为了显示帮助窗口,我们调用OnHelp()方法。 它将垂直拆分两个面板。 我们一定不要忘记再次设置焦点,因为初始焦点会因拆分而丢失。

以下是我们在应用中加载的 HTML 文件。

help.html

<!DOCTYPE html>
<html>

<body bgcolor="#ababab">
<h4>Table of Contents</h4>

<ul>
<li><a href="#basic">Basic statistics</a></li>
<li><a href="#advanced">Advanced statistics</a></li>
<li><a href="#intro">Introducing Newt</a></li>
<li><a href="#charts">Working with charts</a></li>
<li><a href="#pred">Predicting values</a></li>
<li><a href="#neural">Neural networks</a></li>
<li><a href="#glos">Glossary</a></li>
</ul>

<p>
<a name="basic">
<h6>Basic Statistics</h6>
Overview of elementary concepts in statistics.
Variables. Correlation. Measurement scales. Statistical significance. 
Distributions. Normality assumption.
</a>
</p>

<p>
<a name="advanced">
<h6>Advanced Statistics</h6>
Overview of advanced concepts in statistics. Anova. Linear regression. 
Estimation and  hypothesis testing.
Error terms.
</a>
</p>

<p>
<a name="intro">
<h6>Introducing Newt</h6>
Introducing the basic functionality of the Newt application. Creating sheets. 
Charts. Menus and Toolbars. Importing data. Saving data in various formats. 
Exporting data. Shortcuts. List of methods.
</a>
</p>

<p>
<a name="charts">
<h6>Charts</h6>
Working with charts. 2D charts. 3D charts. Bar, line, box, pie, range charts. 
Scatterplots. Histograms.
</a>
</p>

<p>
<a name="pred">
<h6>Predicting values</h6>
Time series and forecasting. Trend Analysis. Seasonality. Moving averages. 
Univariate methods. Multivariate methods. Holt-Winters smoothing. 
Exponential smoothing. ARIMA. Fourier analysis.
</a>
</p>

<p>
<a name="neural">
<h6>Neural networks</h6>
Overview of neural networks. Biology behind neural networks.
Basic artificial Model. Training. Preprocessing. Postprocessing. 
Types of neural networks.
</a>
</p>

<p>
<a name="glos">
<h6>Glossary</h6>
Terms and definitions in statistics.
</a>
</p>

</body>
</html>

HTML 文件包含应用帮助的目录。

图:帮助窗口

wx.ListCtrl小部件

wx.ListCtrl是项目列表的图形表示。 一个wx.ListBox只能有一列。 wx.ListCtrl可以有多个栏。 wx.ListCtrl是一个非常常见且有用的小部件。 例如,文件管理器使用wx.ListCtrl在文件系统上显示目录和文件。 cd 刻录机应用显示要在wx.ListCtrl中刻录的文件。

wx.ListCtrl可以三种不同的格式使用。 在列表视图,报告视图或图标视图中。 这些格式由wx.ListCtrl窗口样式控制。 wx.LC_REPORTwx.LC_LISTwx.LC_ICON

wx.ListCtrl样式

  • wx.LC_LIST
  • wx.LC_REPORT
  • wx.LC_VIRTUAL
  • wx.LC_ICON
  • wx.LC_SMALL_ICON
  • wx.LC_ALIGN_LEFT
  • wx.LC_EDIT_LABELS
  • wx.LC_NO_HEADER
  • wx.LC_SORT_ASCENDING
  • wx.LC_SORT_DESCENDING
  • wx.LC_HRULES
  • wx.LC_VRULES

简单的例子

第一个示例介绍了wx.ListCtrl的一些基本功能。

actresses.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a simple
wx.ListCtrl widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx

data = [('Jessica Alba', 'Pomona', '1981'), ('Sigourney Weaver', 'New York', '1949'),
  ('Angelina Jolie', 'los angeles', '1975'), ('Natalie Portman', 'Jerusalem', '1981'),
  ('Rachel Weiss', 'London', '1971'), ('Scarlett Johansson', 'New York', '1984' )]

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        panel = wx.Panel(self)

        self.list = wx.ListCtrl(panel, wx.ID_ANY, style=wx.LC_REPORT)
        self.list.InsertColumn(0, 'name', width=140)
        self.list.InsertColumn(1, 'place', width=130)
        self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)

        idx = 0

        for i in data:

            index = self.list.InsertItem(idx, i[0])
            self.list.SetItem(index, 1, i[1])
            self.list.SetItem(index, 2, i[2])
            idx += 1

        hbox.Add(self.list, 1, wx.EXPAND)
        panel.SetSizer(hbox)

        self.SetTitle('Actresses')
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该代码示例在wx.ListCtrl中显示有关女演员的数据。

self.list = wx.ListCtrl(panel, wx.ID_ANY, style=wx.LC_REPORT)

我们用wx.LC_REPORT样式创建一个wx.ListCtrl

self.list.InsertColumn(0, 'name', width=140)
self.list.InsertColumn(1, 'place', width=130)
self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)

我们插入三列。 我们可以指定列的width和列的format。 默认格式为wx.LIST_FORMAT_LEFT

idx = 0

for i in data:

    index = self.list.InsertItem(idx, i[0])
    self.list.SetItem(index, 1, i[1])
    self.list.SetItem(index, 2, i[2])
    idx += 1

我们使用两种方法将数据插入wx.ListCtrl。 每行以InsertItem()方法开头。 方法的第一个参数指定行号。 该方法返回行索引。 SetItem()方法将数据添加到当前行的连续列中。

Mixin

Mixins 是进一步增强wx.ListCtrl函数的类。 它们位于wx.lib.mixins.listctrl模块中。 为了使用它们,我们必须从这些类继承。

有六个 mixin:

  • wx.ColumnSorterMixin
  • wx.ListCtrlAutoWidthMixin
  • wx.ListCtrlSelectionManagerMix
  • wx.TextEditMixin
  • wx.CheckListCtrlMixin
  • wx.ListRowHighlighter

wx.ColumnSorterMixin是一个混合器,可以对报表视图中的列进行排序。 wx.ListCtrlAutoWidthMixin类自动将最后一列的大小调整为wx.ListCtrl的末尾。 默认情况下,最后一列不占用剩余空间。 请参阅前面的示例。 wx.ListCtrlSelectionManagerMix定义了平台无关的选择策略。 wx.TextEditMixin可以编辑文本。 wx.CheckListCtrlMixin向每行添加一个复选框。 这样我们可以控制行。 我们可以将每一行设置为选中或取消选中。 wx.ListRowHighlighter处理wx.ListCtrl中交替行的自动背景突出显示。

wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin

以下代码显示了如何使用wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin

autowidth.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we use wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin
with a wx.ListBox.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx
import wx.lib.mixins.listctrl

data = [('Jessica Alba', 'Pomona', '1981'), ('Sigourney Weaver', 'New York', '1949'),
  ('Angelina Jolie', 'Los Angeles', '1975'), ('Natalie Portman', 'Jerusalem', '1981'),
  ('Rachel Weiss', 'London', '1971'), ('Scarlett Johansson', 'New York', '1984')]

class AutoWidthListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):

    def __init__(self, parent, *args, **kw):
        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT)
        wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):        

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        panel = wx.Panel(self)

        self.list = AutoWidthListCtrl(panel)
        self.list.InsertColumn(0, 'name', width=140)
        self.list.InsertColumn(1, 'place', width=130)
        self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)

        idx = 0

        for i in data:

            index = self.list.InsertItem(idx, i[0])
            self.list.SetItem(index, 1, i[1])
            self.list.SetItem(index, 2, i[2])
            idx += 1

        hbox.Add(self.list, 1, wx.EXPAND)
        panel.SetSizer(hbox)

        self.SetTitle('Actresses')
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们稍微改变前面的示例。

import wx.lib.mixins.listctrl

在这里,我们导入 mixin 模块。

class AutoWidthListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin):

    def __init__(self, parent, *args, **kw):
        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT)
        wx.lib.mixins.listctrl.ListCtrlAutoWidthMixin.__init__(self)

我们创建一个新的AutoWidthListCtrl类。 此类从wx.ListCtrlwx.lib.mixins.listctrl.ListCtrlAutoWidthMixin继承。 这称为多重继承。 最后一列将自动调整大小以占据wx.ListCtrl的剩余宽度。

wx.lib.mixins.listctrl.ColumnSorterMixin

以下示例创建可排序的列。 如果单击列标题,则会对列中的相应行进行排序。

sorted.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create sortable columns with
wx.lib.mixins.listctrl.ColumnSorterMixin

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx
import wx.lib.mixins.listctrl

actresses = {
1 : ('Jessica Alba', 'Pomona', '1981'),
2 : ('Sigourney Weaver', 'New York', '1949'),
3 : ('Angelina Jolie', 'Los Angeles', '1975'),
4 : ('Natalie Portman', 'Jerusalem', '1981'),
5 : ('Rachel Weiss', 'London', '1971'),
6 : ('Scarlett Johansson', 'New York', '1984')
}

class SortedListCtrl(wx.ListCtrl, wx.lib.mixins.listctrl.ColumnSorterMixin):

    def __init__(self, parent):

        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT)
        wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, len(actresses))
        self.itemDataMap = actresses

    def GetListCtrl(self):
        return self

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):        

        hbox = wx.BoxSizer(wx.HORIZONTAL)
        panel = wx.Panel(self)

        self.list = SortedListCtrl(panel)
        self.list.InsertColumn(0, 'name', width=140)
        self.list.InsertColumn(1, 'place', width=130)
        self.list.InsertColumn(2, 'year', wx.LIST_FORMAT_RIGHT, 90)

        items = actresses.items()

        idx = 0

        for key, data in items:

            index = self.list.InsertItem(idx, data[0])
            self.list.SetItem(index, 1, data[1])
            self.list.SetItem(index, 2, data[2])
            self.list.SetItemData(index, key)
            idx += 1

        hbox.Add(self.list, 1, wx.EXPAND)
        panel.SetSizer(hbox)

        self.SetTitle('Actresses')
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们将再次在女演员中使用该示例。

wx.lib.mixins.listctrl.ColumnSorterMixin.__init__(self, len(actresses))

wx.lib.mixins.listctrl.ColumnSorterMixin接受一个参数:要排序的列数。

self.itemDataMap = actresses

我们必须将要显示在列表控件中的数据映射到itemDataMap属性。 数据必须为字典数据类型。

def GetListCtrl(self):
    return self

我们必须创建一个GetListCtrl()方法。 此方法返回将要排序的wx.ListCtrl小部件。

self.list.SetItemData(index, key)

我们必须将每行与一个特殊索引相关联。 这是通过SetItemData方法完成的。

wx.lib.mixins.listctrl.CheckListCtrl

可以在列表控件内放置一个复选框。 在 wxPython 中,我们可以使用wx.lib.mixins.listctrl.CheckListCtrl

repository.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we create a check list control widget.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin

packages = [('abiword', '5.8M', 'base'), ('adie', '145k', 'base'),
    ('airsnort', '71k', 'base'), ('ara', '717k', 'base'), ('arc', '139k', 'base'),
    ('asc', '5.8M', 'base'), ('ascii', '74k', 'base'), ('ash', '74k', 'base')]

class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):

    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT |
                wx.SUNKEN_BORDER)
        CheckListCtrlMixin.__init__(self)
        ListCtrlAutoWidthMixin.__init__(self)

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        panel = wx.Panel(self)

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)

        leftPanel = wx.Panel(panel)
        rightPanel = wx.Panel(panel)

        self.log = wx.TextCtrl(rightPanel, style=wx.TE_MULTILINE|wx.TE_READONLY)
        self.list = CheckListCtrl(rightPanel)
        self.list.InsertColumn(0, 'Package', width=140)
        self.list.InsertColumn(1, 'Size')
        self.list.InsertColumn(2, 'Repository')

        idx = 0

        for i in packages:

            index = self.list.InsertItem(idx, i[0])
            self.list.SetItem(index, 1, i[1])
            self.list.SetItem(index, 2, i[2])
            idx += 1

        vbox2 = wx.BoxSizer(wx.VERTICAL)

        selBtn = wx.Button(leftPanel, label='Select All')
        desBtn = wx.Button(leftPanel, label='Deselect All')
        appBtn = wx.Button(leftPanel, label='Apply')

        self.Bind(wx.EVT_BUTTON, self.OnSelectAll, id=selBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnDeselectAll, id=desBtn.GetId())
        self.Bind(wx.EVT_BUTTON, self.OnApply, id=appBtn.GetId())

        vbox2.Add(selBtn, 0, wx.TOP|wx.BOTTOM, 5)
        vbox2.Add(desBtn, 0, wx.BOTTOM, 5)
        vbox2.Add(appBtn)

        leftPanel.SetSizer(vbox2)

        vbox.Add(self.list, 4, wx.EXPAND | wx.TOP, 3)
        vbox.Add((-1, 10))
        vbox.Add(self.log, 1, wx.EXPAND)
        vbox.Add((-1, 10))

        rightPanel.SetSizer(vbox)

        hbox.Add(leftPanel, 0, wx.EXPAND | wx.RIGHT, 5)
        hbox.Add(rightPanel, 1, wx.EXPAND)
        hbox.Add((3, -1))

        panel.SetSizer(hbox)

        self.SetTitle('Repository')
        self.Centre()

    def OnSelectAll(self, event):

        num = self.list.GetItemCount()
        for i in range(num):
            self.list.CheckItem(i)

    def OnDeselectAll(self, event):

        num = self.list.GetItemCount()
        for i in range(num):
            self.list.CheckItem(i, False)

    def OnApply(self, event):

        num = self.list.GetItemCount()

        for i in range(num):

            if i == 0: self.log.Clear()

            if self.list.IsChecked(i):
                self.log.AppendText(self.list.GetItemText(i) + '\n')

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该示例使用wx.lib.mixins.listctrl.CheckListCtrl创建仓库 UI。

class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):

    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT |
                wx.SUNKEN_BORDER)
        CheckListCtrlMixin.__init__(self)
        ListCtrlAutoWidthMixin.__init__(self)

我们从三个不同的类继承。

def OnSelectAll(self, event):

    num = self.list.GetItemCount()

    for i in range(num):
        self.list.CheckItem(i)

OnSelectAll()方法选择所有复选框。 GetItemCount()确定项目数,CheckItem()方法标记当前复选框。

图:存储库

在 wxPython 教程的这一部分中,我们介绍了几个高级小部件,包括wx.ListBoxwx.html.HtmlWindowwx.ListCtrl

wxPython 中的拖放

原文: http://zetcode.com/wxpython/draganddrop/

在计算机图形用户界面中,拖放是单击虚拟对象并将其拖动到其他位置或另一个虚拟对象上的动作(或支持以下动作)。 通常,它可用于调用多种动作,或在两个抽象对象之间创建各种类型的关联。

拖放操作使您可以直观地完成复杂的事情。

在拖放操作中,我们将一些数据从数据源拖动到数据目标。 所以我们必须有:

  • 一些数据
  • 数据来源
  • 数据目标

在 wxPython 中,我们有两个预定义的数据目标:wx.TextDropTargetwx.FileDropTarget

wx.TextDropTarget

wx.TextDropTarget是用于处理文本数据的预定义放置目标。

dragdrop_text.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we drag and drop text data.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

from pathlib import Path
import os
import wx

class MyTextDropTarget(wx.TextDropTarget):

    def __init__(self, object):

        wx.TextDropTarget.__init__(self)
        self.object = object

    def OnDropText(self, x, y, data):

        self.object.InsertItem(0, data)
        return True

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        splitter1 = wx.SplitterWindow(self, style=wx.SP_3D)
        splitter2 = wx.SplitterWindow(splitter1, style=wx.SP_3D)

        home_dir = str(Path.home())

        self.dirWid = wx.GenericDirCtrl(splitter1, dir=home_dir, 
                style=wx.DIRCTRL_DIR_ONLY)

        self.lc1 = wx.ListCtrl(splitter2, style=wx.LC_LIST)
        self.lc2 = wx.ListCtrl(splitter2, style=wx.LC_LIST)

        dt = MyTextDropTarget(self.lc2)
        self.lc2.SetDropTarget(dt)

        self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.lc1.GetId())

        tree = self.dirWid.GetTreeCtrl()

        splitter2.SplitHorizontally(self.lc1, self.lc2, 150)
        splitter1.SplitVertically(self.dirWid, splitter2, 200)

        self.Bind(wx.EVT_TREE_SEL_CHANGED, self.OnSelect, id=tree.GetId())

        self.OnSelect(0)

        self.SetTitle('Drag and drop text')
        self.Centre()

    def OnSelect(self, event):

        list = os.listdir(self.dirWid.GetPath())

        self.lc1.ClearAll()
        self.lc2.ClearAll()

        for i in range(len(list)):

            if list[i][0] != '.':
                self.lc1.InsertItem(0, list[i])

    def OnDragInit(self, event):

        text = self.lc1.GetItemText(event.GetIndex())
        tdo = wx.TextDataObject(text)
        tds = wx.DropSource(self.lc1)

        tds.SetData(tdo)
        tds.DoDragDrop(True)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在示例中,我们在wx.GenericDirCtrl中显示了一个文件系统。 所选目录的内容显示在右上方的列表控件中。 可以将文件名拖放到右下角的列表控件中。

def OnDropText(self, x, y, data):

    self.object.InsertItem(0, data)
    return True    

当我们将文本数据放到目标上时,该数据将通过InsertItem()方法插入到列表控件中。

dt = MyTextDropTarget(self.lc2)
self.lc2.SetDropTarget(dt)  

创建放置目标。 我们使用SetDropTarget()方法将放置目标设置为第二个列表控件。

self.Bind(wx.EVT_LIST_BEGIN_DRAG, self.OnDragInit, id=self.lc1.GetId()) 

当拖动操作开始时,将调用OnDragInit()方法。

def OnDragInit(self, event):

    text = self.lc1.GetItemText(event.GetIndex())
    tdo = wx.TextDataObject(text)
    tds = wx.DropSource(self.lc1)
    ...

OnDragInit()方法中,我们创建一个wx.TextDataObject,其中包含我们的文本数据。 从第一个列表控件创建放置源。

tds.SetData(tdo)
tds.DoDragDrop(True)

我们使用SetData()将数据设置到放置源,并使用DoDragDrop()启动拖放操作。

wx.FileDropTarget

wx.FileDropTarget是接受文件的放置目标,这些文件是从文件管理器中拖动的。

dragdrop_file.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

In this example, we drag and drop files.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx

class FileDrop(wx.FileDropTarget):

    def __init__(self, window):

        wx.FileDropTarget.__init__(self)
        self.window = window

    def OnDropFiles(self, x, y, filenames):

        for name in filenames:

            try:
                file = open(name, 'r')
                text = file.read()
                self.window.WriteText(text)

            except IOError as error:

                msg = "Error opening file\n {}".format(str(error))
                dlg = wx.MessageDialog(None, msg)
                dlg.ShowModal()

                return False

            except UnicodeDecodeError as error:

                msg = "Cannot open non ascii files\n {}".format(str(error))
                dlg = wx.MessageDialog(None, msg)
                dlg.ShowModal()

                return False

            finally:

                file.close()

        return True

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.text = wx.TextCtrl(self, style = wx.TE_MULTILINE)
        dt = FileDrop(self.text)

        self.text.SetDropTarget(dt)

        self.SetTitle('File drag and drop')
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该示例创建一个简单的wx.TextCtrl。 我们可以将文本文件从文件管理器拖到控件中。

def OnDropFiles(self, x, y, filenames):

    for name in filenames:
    ...

我们可以一次拖放多个文件。

try:
    file = open(name, 'r')
    text = file.read()
    self.window.WriteText(text)

我们以只读模式打开文件,获取其内容,然后将内容写入文本控件窗口。

except IOError as error:

    msg = "Error opening file\n {}".format(str(error))
    dlg = wx.MessageDialog(None, msg)
    dlg.ShowModal()

    return False

如果出现输入/输出错误,我们将显示一个消息对话框并终止操作。

self.text = wx.TextCtrl(self, style = wx.TE_MULTILINE)
dt = FileDrop(self.text)

self.text.SetDropTarget(dt)

wx.TextCtrl是放置目标。

在本章中,我们使用了 wxPython 中的拖放操作。

wxPython 图形

原文: http://zetcode.com/wxpython/gdi/

GDI(图形设备接口)是用于处理图形的接口。 它用于与图形设备(例如监视器,打印机或文件)进行交互。 GDI 允许程序员在屏幕或打印机上显示数据,而不必担心特定设备的详细信息。 GDI 使程序员与硬件隔离。

从程序员的角度来看,GDI 是用于处理图形的一组类和方法。 GDI 由 2D 向量图形,字体和图像组成。

The GDI

图:GDI 结构

要开始绘制图形,我们必须创建一个设备上下文(DC)对象。 在 wxPython 中,设备上下文称为wx.DC。 该文档将wx.DC定义为可以在其上绘制图形和文本的设备上下文。 它以通用方式表示设备数量。 同一段代码可以写入不同类型的设备。 无论是屏幕还是打印机。 wx.DC不能直接使用。 相反,程序员应选择派生类之一。 每个派生类都打算在特定条件下使用。

派生的wx.DC

  • wxBufferedDC
  • wxBufferedPaintDC
  • wxPostScriptDC
  • wxMemoryDC
  • wxPrinterDC
  • wxScreenDC
  • wxClientDC
  • wxPaintDC
  • wxWindowDC

wx.ScreenDC用于在屏幕上的任何地方绘制。 如果要在整个窗口上绘制(仅 Windows),则使用wx.WindowDC。 这包括窗口装饰。 wx.ClientDC用于绘制窗口的客户区域。 客户区域是没有装饰(标题和边框)的窗口区域。 wx.PaintDC也用于绘制客户区。 但是wx.PaintDCwx.ClientDC之间有一个区别。 仅可从wx.PaintEvent使用wx.PaintDC。 不应从wx.PaintEvent中使用wx.ClientDCwx.MemoryDC用于在位图上绘制图形。 wx.PostScriptDC用于在任何平台上写入 PostScript 文件。 wx.PrinterDC用于访问打印机(仅 Windows)。

画一条简单的线

我们的第一个示例将在窗口的客户区域上画一条简单的线。

DrawLine(self, x1, y1, x2, y2)

此方法从第一个点到第二个点画一条线。 不包括第二点。

draw_line.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws a line on the
frame window after a while.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        wx.CallLater(2000, self.DrawLine)

        self.SetTitle("Line")
        self.Centre()

    def DrawLine(self):

        dc = wx.ClientDC(self)
        dc.DrawLine(50, 60, 190, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

两秒钟后,我们在框架窗口上画了一条线。

wx.FutureCall(2000, self.DrawLine)

创建窗口后,我们将调用DrawLine()方法。 我们这样做是因为在创建窗口时会绘制它。 因此,我们所有的图纸都将丢失。 我们可以在创建窗口之后开始绘制。 这就是为什么我们调用wx.FutureCall()方法的原因。

def DrawLine(self):

    dc = wx.ClientDC(self)
    dc.DrawLine(50, 60, 190, 60)

我们创建一个wx.ClientDC设备上下文。 唯一的参数是我们要在其上绘制的窗口。 在我们的例子中是self,它是对wx.Frame小部件的引用。 我们称为设备上下文的DrawLine()方法。 该调用实际上在我们的窗口上画了一条线。

了解以下行为非常重要。 如果我们调整窗口大小,该行将消失。 为什么会这样呢? 如果调整了每个窗口的大小,则会重新绘制每个窗口。 如果最大化,它也会被重画。 如果我们用另一个窗口覆盖该窗口,然后再将其打开,则该窗口也会重新绘制。 窗口被绘制为其默认状态,我们的行丢失了。 每次调整窗口大小时,我们都必须画一条线。 解决方法是wx.PaintEvent。 每次重新绘制窗口时都会触发此事件。 我们将在挂钩到 paint 事件的方法内绘制线条。

以下示例显示了它是如何完成的。

draw_line2.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws a line in
a paint event.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Line")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        dc.DrawLine(50, 60, 190, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们画同一条线。 这次是对绘图事件的反应。

self.Bind(wx.EVT_PAINT, self.OnPaint)

在这里,我们将OnPaint方法绑定到wx.PaintEvent事件。 这意味着每次重新绘制窗口时,我们都会调用OnPaint()方法。 现在,如果我们调整窗口大小(覆盖,最大化窗口),该行将不会消失。

dc = wx.PaintDC(self)

注意,这一次我们使用了wx.PaintDC设备上下文。

Drawing a line

图:画一条线

计算机图形

有两种不同的计算机图形:向量和光栅图形。 栅格图形将图像表示为像素的集合。 向量图形是使用诸如点,线,曲线或多边形之类的几何图元来表示图像。 这些基元是使用数学方程式创建的。

两种类型的计算机图形都有优点和缺点。 向量图形优于栅格的优点是:

  • 较小的大小
  • 无限放大的能力
  • 移动,缩放,填充或旋转不会降低图像质量

基本类型

以下是图形基元的部分列表。

  • 线
  • 折线
  • 多边形
  • 圆圈
  • 椭圆
  • 样条

设备上下文属性

设备上下文包含几个属性,例如笔刷,钢笔或字体。 wx.Brush是用于填充区域的绘图工具。 它用于绘制形状的背景。 它具有颜色和样式。 wx.Pen用于绘制形状轮廓。 它具有颜色,宽度和样式。 wx.Font是确定文本外观的对象。

基本要素

在下面的几行中,我们介绍几个基本对象:颜色,画笔,笔,连接,盖帽和渐变。

颜色

颜色是代表红色,绿色和蓝色(RGB)强度值的组合的对象。 有效的 RGB 值在 0 到 255 之间。有三种设置颜色的方法。 我们可以创建wx.Colour对象,使用预定义的颜色名称或十六进制值字符串。 wx.Colour(0,0,255)'BLUE''#0000FF'。 这三个符号产生相同的颜色。

可以在 colorjack.com 网站上找到处理颜色的理想工具。 或者我们可以使用 Gimp 这样的工具。

我们还提供了可在程序中使用的预定义颜色名称的列表。

AQUAMARINE BLACK BLUE BLUE VIOLET BROWN
CADET BLUE CORAL CORNFLOWER BLUE CYAN DARK GREY
DARK GREEN DARK OLIVE GREEN DARK ORCHID DARK SLATE BLUE DARK SLATE GREY
DARK TURQUOISE DIM GREY FIREBRICK FOREST GREEN GOLD
GOLDENROD GREY GREEN GREEN YELLOW INDIAN RED
KHAKI LIGHT BLUE LIGHT GREY LIGHT STEEL BLUE LIME GREEN
MAGENTA MAROON MEDIUM AQUAMARINE MEDIUM BLUE MEDIUM FOREST GREEN
MEDIUM GOLDENROD MEDIUM ORCHID MEDIUM SEA GREEN MEDIUM SLATE BLUE MEDIUM SPRING GREEN
MEDIUM TURQUOISE MEDIUM VIOLET RED MIDNIGHT BLUE NAVY ORANGE
ORANGE RED ORCHID PALE GREEN PINK PLUM
PURPLE RED SALMON SEA GREEN SIENNA
SKY BLUE SLATE BLUE SPRING GREEN STEEL BLUE TAN
THISTLE TURQUOISE VIOLET VIOLET RED WHEAT
WHITE YELLOW YELLOW GREEN

下面的示例使用一些颜色值。

colours.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws nine coloured rectangles
on the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Colours")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        dc.SetPen(wx.Pen('#d4d4d4'))

        dc.SetBrush(wx.Brush('#c56c00'))
        dc.DrawRectangle(10, 15, 90, 60)

        dc.SetBrush(wx.Brush('#1ac500'))
        dc.DrawRectangle(130, 15, 90, 60)

        dc.SetBrush(wx.Brush('#539e47'))
        dc.DrawRectangle(250, 15, 90, 60)

        dc.SetBrush(wx.Brush('#004fc5'))
        dc.DrawRectangle(10, 105, 90, 60)

        dc.SetBrush(wx.Brush('#c50024'))
        dc.DrawRectangle(130, 105, 90, 60)

        dc.SetBrush(wx.Brush('#9e4757'))
        dc.DrawRectangle(250, 105, 90, 60)

        dc.SetBrush(wx.Brush('#5f3b00'))
        dc.DrawRectangle(10, 195, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c'))
        dc.DrawRectangle(130, 195, 90, 60)

        dc.SetBrush(wx.Brush('#785f36'))
        dc.DrawRectangle(250, 195, 90, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们绘制九个矩形,并用不同的颜色填充它们。

dc.SetBrush(wx.Brush('#c56c00'))
dc.DrawRectangle(10, 15, 90, 60)

我们以十六进制表示法指定画笔的颜色。 笔刷是形状的背景填充。 然后,使用DrawRectangle()方法绘制矩形。

Colours

图:颜色

笔是基本的图形对象。 它用于绘制矩形,椭圆形,多边形或其他形状的线,曲线和轮廓。

wx.Pen(wx.Colour colour, width=1, style=wx.SOLID)

wx.Pen构造器具有三个参数:colourwidthstyle。 以下是可能的笔样式的列表:

  • wx.solid
  • wx.DOT
  • wx.LONG_DASH
  • wx.SHORT_DASH
  • wx.DOT_DASH
  • wx.TRANSPARENT

pens.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws six rectangles with different pens.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Pens")
        self.Centre()

    def OnPaint(self, event):
        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen('#4c4c4c', 1, wx.SOLID))
        dc.DrawRectangle(10, 15, 90, 60)

        dc.SetPen(wx.Pen('#4c4c4c', 1, wx.DOT))
        dc.DrawRectangle(130, 15, 90, 60)

        dc.SetPen(wx.Pen('#4c4c4c', 1, wx.LONG_DASH))
        dc.DrawRectangle(250, 15, 90, 60)

        dc.SetPen(wx.Pen('#4c4c4c', 1, wx.SHORT_DASH))
        dc.DrawRectangle(10, 105, 90, 60)

        dc.SetPen(wx.Pen('#4c4c4c', 1, wx.DOT_DASH))
        dc.DrawRectangle(130, 105, 90, 60)

        dc.SetPen(wx.Pen('#4c4c4c', 1, wx.TRANSPARENT))
        dc.DrawRectangle(250, 105, 90, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

如果未指定自定义画笔,则使用默认画笔。 默认画笔为wx.WHITE_BRUSH。 矩形的周长由笔绘制。 最后一个没有边界。 它是透明的,即不可见。

Pens

图:笔

连接和盖帽

笔对象具有其他两个参数:连接和盖帽。 连接定义线之间的连接如何绘制。 连接样式具有以下选项:

  • wx.JOIN_MITER
  • wx.JOIN_BEVEL
  • wx.JOIN_ROUND

使用wx.JOIN_MITER时,线条的外边缘会延伸。 他们以一个角度相遇,并且该区域被填充。 在wx.JOIN_BEVEL中,两条线之间的三角形缺口被填充。 在wx.JOIN_ROUND中,填充了两条线之间的圆弧。 默认值为wx.JOIN_ROUND

笔帽定义了笔将如何绘制线条的末端。 选项包括:

  • wx.CAP_ROUND
  • wx.CAP_PROJECTING
  • wx.CAP_BUTT

wx.CAP_ROUND绘制圆形末端。 wx.CAP_PROJECTINGwx.CAP_BUTT画出方形末端。 它们之间的区别是wx.CAP_PROJECTING将超出端点超出行大小的一半。 wx.CAP_ROUND也将延伸到终点之外。

joins_caps.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws uses different joins
and caps in drawing.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Joins and caps")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        pen = wx.Pen('#4c4c4c', 10, wx.SOLID)

        pen.SetJoin(wx.JOIN_MITER)
        dc.SetPen(pen)
        dc.DrawRectangle(15, 15, 80, 50)

        pen.SetJoin(wx.JOIN_BEVEL)
        dc.SetPen(pen)
        dc.DrawRectangle(125, 15, 80, 50)

        pen.SetJoin(wx.JOIN_ROUND)
        dc.SetPen(pen)
        dc.DrawRectangle(235, 15, 80, 50)

        pen.SetCap(wx.CAP_BUTT)
        dc.SetPen(pen)
        dc.DrawLine(30, 150,  150, 150)

        pen.SetCap(wx.CAP_PROJECTING)
        dc.SetPen(pen)
        dc.DrawLine(30, 190,  150, 190)

        pen.SetCap(wx.CAP_ROUND)
        dc.SetPen(pen)
        dc.DrawLine(30, 230,  150, 230)

        pen2 = wx.Pen('#4c4c4c', 1, wx.SOLID)
        dc.SetPen(pen2)
        dc.DrawLine(30, 130, 30, 250)
        dc.DrawLine(150, 130, 150, 250)
        dc.DrawLine(155, 130, 155, 250)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

pen = wx.Pen('#4c4c4c', 10, wx.SOLID)

为了查看各种连接样式和盖帽样式,我们需要将笔的宽度设置为大于 1。

dc.DrawLine(150, 130, 150, 250)
dc.DrawLine(155, 130, 155, 250)

注意两条封闭的垂直线。 它们之间的距离是 5px。 恰好是当前笔宽的一半。

Joins and Caps

图:连接和盖帽

渐变

在计算机图形学中,渐变是从浅到深或从一种颜色到另一种颜色的阴影的平滑混合。 在 2D 绘图程序和绘图程序中,渐变用于创建彩色背景和特殊效果以及模拟灯光和阴影。

GradientFillLinear(self, rect, initialColour, destColour, nDirection=RIGHT)

此方法使用线性渐变填充rect指定的区域,该区域从initialColour开始并最终逐渐变为destColournDirection参数指定颜色改变的方向; 默认值为wx.EAST

gradients.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws four rectangles filled
with gradients.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Gradients")
        self.Centre()

    def OnPaint(self, event):

        dc = wx.PaintDC(self)

        dc.GradientFillLinear((20, 20, 180, 40), '#ffec00', '#000000', wx.NORTH)
        dc.GradientFillLinear((20, 80, 180, 40), '#ffec00', '#000000', wx.SOUTH)
        dc.GradientFillLinear((20, 140, 180, 40), '#ffec00', '#000000', wx.EAST)
        dc.GradientFillLinear((20, 200, 180, 40), '#ffec00', '#000000', wx.WEST)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在该示例中,四个矩形填充有渐变。

Gradients

图:渐变

wx.brush

画笔是基本的图形对象。 它用于绘制图形形状的背景,例如矩形,椭圆形或多边形。

wxPython 具有以下内置画笔类型:

  • wx.SOLID
  • wx.STIPPLE
  • wx.BDIAGONAL_HATCH
  • wx.CROSSDIAG_HATCH
  • wx.FDIAGONAL_HATCH
  • wx.CROSS_HATCH
  • wx.HORIZONTAL_HATCH
  • wx.VERTICAL_HATCH
  • wx.TRANSPARENT

brushes.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws eight rectangles filled
with different brushes.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Brushes")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.CROSS_HATCH))
        dc.DrawRectangle(10, 15, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.SOLID))
        dc.DrawRectangle(130, 15, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.BDIAGONAL_HATCH))
        dc.DrawRectangle(250, 15, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.CROSSDIAG_HATCH))
        dc.DrawRectangle(10, 105, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.FDIAGONAL_HATCH))
        dc.DrawRectangle(130, 105, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.HORIZONTAL_HATCH))
        dc.DrawRectangle(250, 105, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.VERTICAL_HATCH))
        dc.DrawRectangle(10, 195, 90, 60)

        dc.SetBrush(wx.Brush('#4c4c4c', wx.TRANSPARENT))
        dc.DrawRectangle(130, 195, 90, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在示例中使用了八种不同的内置画笔类型。

Brushes

图:笔刷

自定义模式

我们不限于使用预定义的模式。 我们可以轻松创建自己的自定义模式。

custom_patterns.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws three rectangles with custom
brush patterns.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Custom patterns")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen('#C7C3C3'))

        brush1 = wx.Brush(wx.Bitmap('pattern1.png'))
        dc.SetBrush(brush1)
        dc.DrawRectangle(10, 15, 90, 60)

        brush2 = wx.Brush(wx.Bitmap('pattern2.png'))
        dc.SetBrush(brush2)
        dc.DrawRectangle(130, 15, 90, 60)

        brush3 = wx.Brush(wx.Bitmap('pattern3.png'))
        dc.SetBrush(brush3)
        dc.DrawRectangle(250, 15, 90, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们创建了一些小的位图。 位图是在 Gimp 中创建的。

brush1 = wx.Brush(wx.Bitmap('pattern1.png'))
dc.SetBrush(brush1)
dc.DrawRectangle(10, 15, 90, 60)

从位图创建画笔,并将其设置为设备上下文。 它用于填充矩形的内部。

Custom Patterns

图:自定义模式

最简单的几何对象是一个点。 它是窗口上的一个普通点。

DrawPoint(self, x, y)

此方法在 x,y 坐标处绘制点。

points.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws one thousand points
randomly on the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
import random

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Points")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen('RED'))

        for i in range(1000):

            w, h = self.GetSize()
            x = random.randint(1, w-1)
            y = random.randint(1, h-1)
            dc.DrawPoint(x, y)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

一个点可能很难看清,因此我们创建了 1000 个点。

dc.SetPen(wx.Pen('RED'))

在这里,我们将笔的颜色设置为红色。

w, h = self.GetSize()
x = random.randint(1, w-1)

这些点在窗口的客户区域周围随机分布。 它们也可以动态分配。 如果我们调整窗口的大小,将在新的客户端大小上随机绘制点。 randint(a, b)方法返回范围为[a,b]的随机整数,例如包括两点。

Points

图:绘制点

形状

形状是更复杂的几何对象。 在以下示例中,我们绘制了各种几何形状。

shapes.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws various shapes on
the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Shapes")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        dc.SetBrush(wx.Brush('#777'))
        dc.SetPen(wx.Pen("#777"))

        dc.DrawEllipse(20, 20, 90, 60)
        dc.DrawRoundedRectangle(130, 20, 90, 60, 10)
        dc.DrawArc(240, 40, 340, 40, 290, 20)

        dc.DrawRectangle(20, 120, 80, 50)
        dc.DrawPolygon(((130, 140), (180, 170), (180, 140), (220, 110), (140, 100)))
        dc.DrawSpline(((240, 170), (280, 170), (285, 110), (325, 110)))

        dc.DrawLines(((20, 260), (100, 260), (20, 210), (100, 210)))
        dc.DrawCircle(170, 230, 35)
        dc.DrawRectangle(250, 200, 60, 60)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在我们的示例中,我们绘制了一个椭圆,一个圆角矩形,一个圆弧,一个矩形,一个多边形,样条曲线,线,一个圆和一个正方形。 圆形是一种特殊的椭圆,而正方形是一种特殊的矩形。

Shapes

图:形状

区域

设备上下文可以分为几个部分,称为区域。 区域可以是任何形状,例如矩形或圆形。 使用UnionIntersectSubstractXor操作,我们可以创建复杂区域。 区域用于概述,填充和裁剪。

我们可以通过三种方式创建区域。 最简单的方法是创建一个矩形区域。 可以从位图的点列表创建更复杂的区域。

在前往区域之前,我们将首先创建一个小示例。 我们将该主题分为几个部分,以便于理解。 您可能会发现修改学校数学是一个好主意。 在这里我们可以找到一篇不错的文章。

lines.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program draws various shapes on
the window.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
from math import hypot, sin, cos, pi

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle('Lines')
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)
        size_x, size_y = self.GetClientSize()
        dc.SetDeviceOrigin(size_x/2, size_y/2)

        radius = hypot(size_x/2, size_y/2)
        angle = 0

        while (angle < 2*pi):
            x = radius*cos(angle)
            y = radius*sin(angle)
            dc.DrawLine((0, 0), (x, y))
            angle = angle + 2*pi/360

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,我们从客户区域的中间绘制了 360 条线。 两条线之间的距离是 1 度。 我们创造了一个有趣的人物。

import wx
from math import hypot, sin, cos, pi

我们需要数学模块中的三个数学函数和一个常数。

dc.SetDeviceOrigin(size_x/2, size_y/2)

方法SetDeviceOrigin()创建坐标系的新起点。 我们将其放入客户区的中间。 通过重新定位坐标系,我们使图形的复杂程度降低了。

radius = hypot(size_x/2, size_y/2)

在这里,我们得到了斜边。 这是最长的线,我们可以从客户区域的中间绘制。 它是从开始到窗口角落的线段长度。 这样,大多数线条都无法完全画出。 重叠部分不可见。 参见斜边。

x = radius*cos(angle)
y = radius*sin(angle)

这些是参数函数。 它们用于在曲线上找到[x,y]点。 从坐标系的开始一直到圆上的点绘制所有 360 条线。

Lines

图:直线

剪裁

Clipping将绘图限制在特定区域。 裁剪通常用于创建效果并改善应用的性能。 我们使用SetClippingRegionAsRegion()方法将绘图限制在特定区域。

在下面的示例中,我们将修改和增强我们以前的程序。

star.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program demonstrates a clipping operation
when drawing a star object.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
from math import hypot, sin, cos, pi

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)

        self.SetTitle("Star")
        self.Centre()

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetPen(wx.Pen('#424242'))
        size_x, size_y = self.GetClientSize()
        dc.SetDeviceOrigin(size_x/2, size_y/2)

        points = (((0, 85), (75, 75), (100, 10), (125, 75), (200, 85),
            (150, 125), (160, 190), (100, 150), (40, 190), (50, 125)))

        region = wx.Region(points)
        dc.SetDeviceClippingRegion(region)

        radius = hypot(size_x/2, size_y/2)
        angle = 0

        while (angle < 2*pi):

            x = radius*cos(angle)
            y = radius*sin(angle)
            dc.DrawLine((0, 0), (x, y))
            angle = angle + 2*pi/360

        dc.DestroyClippingRegion()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们再次绘制所有 360 线。 但是这次,只绘制了一部分客户区域。 我们将绘图限制到的区域是星形对象。

region = wx.Region(points)
dc.SetDeviceClippingRegion(region)

我们从点列表创建一个区域。 SetDeviceClippingRegion()方法将图形限制在指定的区域。 在我们的情况下,它是一个恒星对象。

dc.DestroyClippingRegion()

我们必须销毁剪切区域。

Star

图:星星

区域操作

可以组合区域以创建更复杂的形状。 我们可以使用四个集合运算:并集,相交,减法,异或。

以下示例显示了所有正在执行的四个操作。

region_operations.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program performs set operations on regions.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

         self.Bind(wx.EVT_PAINT, self.OnPaint)

         self.SetTitle("Regions")
         self.Centre()

    def OnPaint(self, e):

         dc = wx.PaintDC(self)
         dc.SetPen(wx.Pen('#d4d4d4'))

         dc.DrawRectangle(20, 20, 50, 50)
         dc.DrawRectangle(30, 40, 50, 50)

         dc.SetBrush(wx.Brush('#ffffff'))
         dc.DrawRectangle(100, 20, 50, 50)
         dc.DrawRectangle(110, 40, 50, 50)

         region1 = wx.Region(100, 20, 50, 50)
         region2 = wx.Region(110, 40, 50, 50)
         region1.Intersect(region2)

         rect = region1.GetBox()
         dc.SetDeviceClippingRegion(region1)
         dc.SetBrush(wx.Brush('#ff0000'))
         dc.DrawRectangle(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush('#ffffff'))
         dc.DrawRectangle(180, 20, 50, 50)
         dc.DrawRectangle(190, 40, 50, 50)

         region1 = wx.Region(180, 20, 50, 50)
         region2 = wx.Region(190, 40, 50, 50)
         region1.Union(region2)
         dc.SetDeviceClippingRegion(region1)

         rect = region1.GetBox()
         dc.SetBrush(wx.Brush('#fa8e00'))
         dc.DrawRectangle(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush('#ffffff'))
         dc.DrawRectangle(20, 120, 50, 50)
         dc.DrawRectangle(30, 140, 50, 50)
         region1 = wx.Region(20, 120, 50, 50)
         region2 = wx.Region(30, 140, 50, 50)
         region1.Xor(region2)

         rect = region1.GetBox()
         dc.SetDeviceClippingRegion(region1)
         dc.SetBrush(wx.Brush('#619e1b'))
         dc.DrawRectangle(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush('#ffffff'))
         dc.DrawRectangle(100, 120, 50, 50)
         dc.DrawRectangle(110, 140, 50, 50)
         region1 = wx.Region(100, 120, 50, 50)
         region2 = wx.Region(110, 140, 50, 50)
         region1.Subtract(region2)

         rect = region1.GetBox()
         dc.SetDeviceClippingRegion(region1)
         dc.SetBrush(wx.Brush('#715b33'))
         dc.DrawRectangle(rect)
         dc.DestroyClippingRegion()

         dc.SetBrush(wx.Brush('#ffffff'))
         dc.DrawRectangle(180, 120, 50, 50)
         dc.DrawRectangle(190, 140, 50, 50)
         region1 = wx.Region(180, 120, 50, 50)
         region2 = wx.Region(190, 140, 50, 50)
         region2.Subtract(region1)

         rect = region2.GetBox()
         dc.SetDeviceClippingRegion(region2)
         dc.SetBrush(wx.Brush('#0d0060'))
         dc.DrawRectangle(rect)
         dc.DestroyClippingRegion()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在示例中,我们提出了六个区域设置操作。

 region1 = wx.Region(100, 20, 50, 50)
 region2 = wx.Region(110, 40, 50, 50)
 region1.Intersect(region2)

此代码在两个区域上执行交叉操作。

Set operations on regions

图:区域上的设置操作

映射模式

映射模式定义用于将页面空间单位转换为设备空间单位的度量单位,并且还定义设备的 x 和 y 轴的方向。

用英语说,用公制衡量

英语成为全球交流的语言。 度量系统也因此成为度量系统中的全局系统。 根据维基百科文章的介绍,只有三个例外。 美国,利比里亚和缅甸。 例如,美国人用华氏温度来测量温度,用加仑来加油或用磅来称重。

即使我们在欧洲使用公制,也有例外。 美国主导着 IT,我们正在导入其标准。 所以我们也说我们有一台 17 英寸的显示器。 图形可以放入文件中,可以在监视器或其他设备(相机,摄像机,移动电话)的屏幕上显示,也可以用打印机进行打印。 纸张大小可以以毫米,点或英寸为单位设置,屏幕的分辨率以像素为单位,文本的质量取决于每英寸的点数。 我们也有点,位或样本。 这是我们拥有逻辑和设备单元的原因之一。

逻辑和设备单元

如果在客户区上绘制文本或几何图元,则使用逻辑单元对其进行定位。

如果要绘制一些文本,请提供text参数和 x,y 位置。 x,y 以逻辑单位表示。 然后,设备以设备为单位绘制文本。 逻辑和设备单元可以相同,也可以不同。 逻辑单位由人(毫米)使用,设备单位是particular设备固有的。 例如,屏幕的本机设备单位是像素。 HEWLETT PACKARD LaserJet 1022的本机设备单位为 1200dpi(每英寸点数)。

到目前为止,我们已经讨论了各种度量单位。 设备的映射模式是一种将逻辑单元转换为设备单元的方法。 wxPython 具有以下映射模式:

映射模式 逻辑单元
wx.MM_TEXT 1 像素
wx.MM_METRIC 1 毫米
wx.MM_LOMETRIC 1/10 毫米
wx.MM_POINTS 1 点,1/72 英寸
wx.MM_TWIPS 点的 1/20 或 1/1440 英寸

默认的映射模式是wx.MM_TEXT。 在此模式下,逻辑单元与设备单元相同。 人们将对象放置在屏幕上或设计网页时,他们通常以像素为单位思考。 Web 设计人员创建三列页面,这些列以像素为单位设置。 页面的最低公分母通常是 800px 等。这种想法很自然,因为我们知道我们的显示器具有1024x768像素,我们不打算进行转换,而是习惯于以像素为单位进行思考。 如果要以毫米为单位绘制结构,则可以使用两种度量映射模式。 对于屏幕而言,以毫米为单位直接绘制太厚了,这就是为什么我们要使用wx.MM_LOMETRIC映射模式。

要设置不同的映射模式,我们使用SetMapMode()方法。

标尺示例

标尺以像素为单位测量屏幕对象。

ruler.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a ruler.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

RW = 701 # ruler width
RM = 10  # ruler margin
RH = 80  # ruler height

class Example(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent, size=(RW + 2*RM, RH),
            style=wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.STAY_ON_TOP)
        self.font = wx.Font(7, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
            wx.FONTWEIGHT_BOLD, False, 'Courier 10 Pitch')

        self.InitUI()

    def InitUI(self):

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown)
        self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp)
        self.Bind(wx.EVT_RIGHT_DOWN, self.OnRightDown)
        self.Bind(wx.EVT_MOTION, self.OnMouseMove)

        self.Centre()
        self.Show(True)

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        brush = wx.Brush(wx.Bitmap('granite.png'))
        dc.SetBrush(brush)
        dc.DrawRectangle(0, 0, RW+2*RM, RH)
        dc.SetFont(self.font)

        dc.SetPen(wx.Pen('#F8FF25'))
        dc.SetTextForeground('#F8FF25')

        for i in range(RW):

            if not (i % 100):

                dc.DrawLine(i+RM, 0, i+RM, 10)
                w, h = dc.GetTextExtent(str(i))
                dc.DrawText(str(i), i+RM-w/2, 11)

            elif not (i % 20):

                dc.DrawLine(i+RM, 0, i+RM, 8)

            elif not (i % 2):

                dc.DrawLine(i+RM, 0, i+RM, 4)

    def OnLeftDown(self, e):

        x, y = self.ClientToScreen(e.GetPosition())
        ox, oy = self.GetPosition()

        dx = x - ox
        dy = y - oy

        self.delta = ((dx, dy))

    def OnMouseMove(self, e):

        if e.Dragging() and e.LeftIsDown():

            self.SetCursor(wx.Cursor(wx.CURSOR_HAND))

            x, y = self.ClientToScreen(e.GetPosition())
            fp = (x - self.delta[0], y - self.delta[1])
            self.Move(fp)

    def OnLeftUp(self, e):

        self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

    def OnRightDown(self, e):

        self.Close()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

在此示例中,我们创建一个标尺。 此标尺以像素为单位测量屏幕对象。 我们保留了默认的映射模式,即wx.MM_TEXT。 正如我们已经提到的,此模式具有相同的逻辑和设备单元。 在我们的例子中,这些是像素。

def __init__(self, parent):
    wx.Frame.__init__(self, parent, size=(RW + 2*RM, RH),
        style=wx.FRAME_NO_TASKBAR | wx.NO_BORDER | wx.STAY_ON_TOP)

我们创建了一个无边界的窗口。 标尺宽 721 像素:RW + 2 * RM = 701 + 20 = 721。标尺显示 700 个数字;0 ... 700为 701 像素。 标尺两边都有空白,2 * 10是 20 像素。 两者合计为 721 像素。

brush = wx.Brush(wx.Bitmap('granite.png'))
dc.SetBrush(brush)
dc.DrawRectangle(0, 0, RW+2*RM, RH)

在这里,我们在窗口上绘制一个自定义模式。 我们使用了 Gimp 中可用的预定义模式。 它被称为花岗岩。

w, h = dc.GetTextExtent(str(i))
dc.DrawText(str(i), i+RM-w/2, 11)

这些行确保我们正确对齐文本。 GetTextExtent()方法返回文本的宽度和高度。

窗口周围没有边框。 因此,我们必须手动处理移动。 OnLeftDown()OnMouseMove()方法使我们能够移动标尺。

def OnLeftDown(self, e):

    x, y = self.ClientToScreen(e.GetPosition())
    ox, oy = self.GetPosition()

    dx = x - ox
    dy = y - oy

    self.delta = ((dx, dy))

OnLeftDown()方法中,我们确定窗口和鼠标光标的坐标;delta值是鼠标指针距窗口左上角的距离。 我们需要delta值才能移动窗口。

def OnMouseMove(self, e):

    if e.Dragging() and e.LeftIsDown():

        self.SetCursor(wx.Cursor(wx.CURSOR_HAND))

        x, y = self.ClientToScreen(e.GetPosition())
        fp = (x - self.delta[0], y - self.delta[1])
        self.Move(fp)

当我们同时拖动窗口并按下鼠标左键时,将执行该代码。 在代码块中,我们使用SetCursor()更改鼠标光标,并使用Move()方法移动窗口。 增量值用于获取距离。

def OnLeftUp(self, e):

    self.SetCursor(wx.Cursor(wx.CURSOR_ARROW))

释放鼠标左键时,将光标改回到箭头。

def OnRightDown(self, e):

    self.Close()

右键单击窗口区域可关闭窗口。

Ruler example

图:标尺示例

在本章中,我们使用了 wxPython 中的图形。

Windows API 对话框

原文: http://zetcode.com/gui/winapi/dialogs/

对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。对话框是用户与计算机程序之间进行通信的重要手段。

非模态对话框

非模态对话框不会限制您使用特定的窗口。 用户可以在对话框和程序的其他窗口之间切换。 典型的非模态对话框是“查找和替换”对话框或浮动工具栏。

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK DialogProc(HWND, UINT, WPARAM, LPARAM);

void CreateDialogBox(HWND);
void RegisterDialogClass(HWND);

HINSTANCE ghInstance;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PWSTR pCmdLine, int nCmdShow) {

  MSG  msg;    
  HWND hwnd;

  WNDCLASSW wc = {0};

  wc.lpszClassName = L"Window";
  wc.hInstance     = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;

  RegisterClassW(&wc);
  hwnd = CreateWindowW(wc.lpszClassName, L"Window",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 250, 150, NULL, NULL, hInstance, NULL);  

  ghInstance = hInstance;

  while( GetMessage(&msg, NULL, 0, 0)) {
    DispatchMessage(&msg);
  }

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

  switch(msg) {

      case WM_CREATE:
          RegisterDialogClass(hwnd);
          CreateWindowW(L"button", L"Show dialog",    
              WS_VISIBLE | WS_CHILD ,
              20, 50, 95, 25, hwnd, (HMENU) 1, NULL, NULL);  
          break;

      case WM_COMMAND:
          CreateDialogBox(hwnd);
          break;

      case WM_DESTROY:
      {
          PostQuitMessage(0);
          return 0;
      }
  }
  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK DialogProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg) {

    case WM_CREATE:
        CreateWindowW(L"button", L"Ok",    
          WS_VISIBLE | WS_CHILD ,
          50, 50, 80, 25, hwnd, (HMENU) 1, NULL, NULL);  
    break;

    case WM_COMMAND:
        DestroyWindow(hwnd);
    break;

    case WM_CLOSE:
        DestroyWindow(hwnd);
        break;

  }

  return (DefWindowProcW(hwnd, msg, wParam, lParam));
}

void RegisterDialogClass(HWND hwnd) {

  WNDCLASSEXW wc = {0};
  wc.cbSize           = sizeof(WNDCLASSEXW);
  wc.lpfnWndProc      = (WNDPROC) DialogProc;
  wc.hInstance        = ghInstance;
  wc.hbrBackground    = GetSysColorBrush(COLOR_3DFACE);
  wc.lpszClassName    = L"DialogClass";
  RegisterClassExW(&wc);

}

void CreateDialogBox(HWND hwnd) {

  CreateWindowExW(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST,  L"DialogClass", L"Dialog Box", 
        WS_VISIBLE | WS_SYSMENU | WS_CAPTION , 100, 100, 200, 150, 
        NULL, NULL, ghInstance,  NULL);
}

对话框只是一种特殊的窗口。 它被创建为带有某些特定标志的普通窗口。

CreateWindowExW(WS_EX_DLGMODALFRAME | WS_EX_TOPMOST,  L"DialogClass", L"Dialog Box", 
    WS_VISIBLE | WS_SYSMENU | WS_CAPTION , 100, 100, 200, 150, 
    NULL, NULL, ghInstance,  NULL);

使用常规标志WS_VISIBLE | WS_SYSMENU | WS_CAPTION和扩展标志WS_EX_DLGMODALFRAME | WS_EX_TOPMOST的组合创建对话框。

Modeless dialog

图:非模态对话

常用对话框

这些是用于执行常见任务的对话框。 打开和保存文件,打印文档,选择颜色等。通用对话框为程序员节省了大量工作。 它们有助于促进应用中的标准。

颜色对话框

这是选择颜色的常用对话框。

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK PanelProc(HWND, UINT, WPARAM, LPARAM);

void RegisterPanel(void);
COLORREF ShowColorDialog(HWND);

COLORREF gColor = RGB(255, 255, 255);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PWSTR pCmdLine, int nCmdShow) {

  MSG  msg ;    
  WNDCLASSW wc = {0};
  wc.lpszClassName = L"Color dialog box";
  wc.hInstance     = hInstance;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc;

  RegisterClassW(&wc);
  CreateWindowW( wc.lpszClassName, L"Color dialog box",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                150, 150, 250, 200, 0, 0, hInstance, 0);  

  while( GetMessage(&msg, NULL, 0, 0)) {
    DispatchMessage(&msg);
  }

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

  static HWND hwndPanel;

  switch(msg) {

    case WM_CREATE:
    {
        CreateWindowW(L"button", L"Color",
           WS_VISIBLE | WS_CHILD ,
           20, 30, 80, 25,
           hwnd, (HMENU) 1, NULL, NULL);

        RegisterPanel();
        hwndPanel = CreateWindowW(L"Panel", NULL, 
                    WS_CHILD | WS_VISIBLE,
                    130, 30, 80, 80, hwnd, (HMENU) 2, NULL, NULL);   
        break;
    }

    case WM_COMMAND:
    {
        gColor = ShowColorDialog(hwnd);
        InvalidateRect(hwndPanel, NULL, TRUE);    
        break;
    }

    case WM_DESTROY:
    {
        PostQuitMessage(0);
        break;
    }
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

LRESULT CALLBACK PanelProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

  HDC hdc;
  PAINTSTRUCT ps; 
  RECT rect;

  switch(msg) {

    case WM_PAINT:
    {
        GetClientRect(hwnd, &rect);
        hdc = BeginPaint(hwnd, &ps);
        SetBkColor(hdc, gColor);
        ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rect, "", 0, NULL);
        EndPaint(hwnd, &ps);
        break;
    }
  }

  return DefWindowProc(hwnd, msg, wParam, lParam);
}

COLORREF ShowColorDialog(HWND hwnd) {

  CHOOSECOLOR cc;
  static COLORREF crCustClr[16];

  ZeroMemory(&cc, sizeof(cc));
  cc.lStructSize = sizeof(cc);
  cc.hwndOwner = hwnd;
  cc.lpCustColors = (LPDWORD) crCustClr;
  cc.rgbResult = RGB(0, 255, 0);
  cc.Flags = CC_FULLOPEN | CC_RGBINIT;
  ChooseColor(&cc);

  return cc.rgbResult;
}

void RegisterPanel(void) {

  WNDCLASSW rwc = {0};
  rwc.lpszClassName = L"Panel";
  rwc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
  rwc.lpfnWndProc   = PanelProc;
  RegisterClassW(&rwc);
}

在我们的示例中,我们有一个按钮控件和一个子窗口。 子窗口的颜色在开始时为白色。 我们可以通过按下按钮并选择自定义颜色值来更改子窗口的颜色。

COLORREF gColor = RGB(255, 255, 255);

我们定义一个全局颜色值; 默认情况下为白色。

gColor = ShowColorDialog(hwnd);

颜色对话框显示在ShowColorDialog()用户函数中。 该函数返回所选的颜色值。

CHOOSECOLOR cc; 

要创建颜色对话框,我们必须定义并填充CHOOSECOLOR结构。

cc.rgbResult = RGB(0, 255, 0);
cc.Flags = CC_FULLOPEN | CC_RGBINIT;

如果我们提供CC_RGBINIT,则显示对话框时,rgbResult成员是最初选择的颜色。 如果用户单击确定按钮,则rgbResult指定用户的颜色选择。

ChooseColor(&cc);

显示颜色对话框。

gColor = ShowColorDialog(hwnd);
InvalidateRect(hwndPanel, NULL, TRUE); 

获得颜色值后,我们调用InvalidateRect()函数。 此函数会将WM_PAINT消息发送到我们的子窗口。

SetBkColor(hdc, gColor);
ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rect, "", 0, NULL);

在面板过程中,我们更改子窗口的背景色。 除了在窗口上显示文本之外,ExtTextOut()函数还可以更改窗口的背景色。 我们不会显示任何文本,我们只会更改背景颜色。 如果提供ETO_OPAQUE标志,则ExtTextOut()函数将使用SetBkColor()函数指定的颜色。

Color dialog box

图:颜色对话框 box

文件打开对话框

这是打开文件的常用对话框。 不要使用 UNICODE 编译此示例。

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateMenubar(HWND);
void OpenDialog(HWND);
void LoadFile(LPSTR);

#define IDM_FILE_NEW 1
HWND ghwndEdit;

int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, 
    LPSTR lpCmdLine, int nCmdShow) {

  MSG  msg ;    
  WNDCLASS wc = {0};
  wc.lpszClassName = TEXT( "Opendialog" );
  wc.hInstance     = hInstance ;
  wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
  wc.lpfnWndProc   = WndProc ;
  wc.hCursor       = LoadCursor(0, IDC_ARROW);

  RegisterClass(&wc);
  CreateWindow( wc.lpszClassName, TEXT("Opendialog"),
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                150, 150, 265, 200, 0, 0, hInstance, 0);  

  while( GetMessage(&msg, NULL, 0, 0)) {
    DispatchMessage(&msg);
  }

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

  switch(msg) {

      case WM_CREATE:
          ghwndEdit = CreateWindowEx(WS_EX_RIGHTSCROLLBAR, TEXT("edit"), NULL,
                    WS_VISIBLE | WS_CHILD | WS_HSCROLL | WS_VSCROLL | ES_MULTILINE,
                    0, 0, 260, 180,
                    hwnd, (HMENU) 1, NULL, NULL);

          CreateMenubar(hwnd);
          break;

      case WM_SIZE:
          SetWindowPos(ghwndEdit, NULL, 0, 0, LOWORD(lParam), HIWORD(lParam),
             SWP_NOMOVE | SWP_NOZORDER);
          break;

      case WM_COMMAND:
          if (wParam==IDM_FILE_NEW) {
              OpenDialog(hwnd);
          }
          break;

      case WM_DESTROY:
          PostQuitMessage(0);
          break;
  }

  return DefWindowProc(hwnd, msg, wParam, lParam);
}

void CreateMenubar(HWND hwnd) {

  HMENU hMenubar;
  HMENU hMenu;

  hMenubar = CreateMenu();
  hMenu = CreateMenu();
  AppendMenu(hMenubar, MF_POPUP, (UINT_PTR)hMenu, TEXT("&File"));
  AppendMenu(hMenu, MF_STRING, IDM_FILE_NEW, TEXT("&Open"));
  SetMenu(hwnd, hMenubar);
}

void OpenDialog(HWND hwnd) {

  OPENFILENAME ofn;
  TCHAR szFile[MAX_PATH];

  ZeroMemory(&ofn, sizeof(ofn));
  ofn.lStructSize = sizeof(ofn);
  ofn.lpstrFile = szFile;
  ofn.lpstrFile[0] = '\0';
  ofn.hwndOwner = hwnd;
  ofn.nMaxFile = sizeof(szFile);
  ofn.lpstrFilter = TEXT("All files(*.*)\0*.*\0");
  ofn.nFilterIndex = 1;
  ofn.lpstrInitialDir = NULL;
  ofn.lpstrFileTitle = NULL;
  ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

  if(GetOpenFileName(&ofn))
      LoadFile(ofn.lpstrFile);
}

void LoadFile(LPSTR file) {

  HANDLE hFile;
  DWORD dwSize;
  DWORD dw;

  LPBYTE lpBuffer = NULL;

  hFile = CreateFile(file, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
  dwSize = GetFileSize(hFile, NULL);
  lpBuffer = (LPBYTE) HeapAlloc(GetProcessHeap(), 
      HEAP_GENERATE_EXCEPTIONS, dwSize + 1);
  ReadFile(hFile, (LPWSTR)lpBuffer, dwSize, &dw, NULL);
  CloseHandle(hFile);
  lpBuffer[dwSize] = 0;
  SetWindowText(ghwndEdit, (LPSTR) lpBuffer);
  HeapFree(GetProcessHeap(), 0, lpBuffer);
}

在这个例子中,我们创建一个带有多行编辑控件的窗口。

要创建一个文件打开对话框,我们创建并填充OPENFILENAME结构。

ofn.lpstrFile = szFile;

如果OpenFileName()函数返回TRUE,则所选文件的名称位于lpstrFile成员中。

ofn.lpstrFilter = TEXT("All files(*.*)\0*.*\0");

这定义了文件过滤器。 在我们的示例中,对话框显示所有文件类型。

ofn.nFilterIndex = 1;

在“文件类型”组合框控件中指定当前所选过滤器的索引。

if(GetOpenFileName(&ofn))
    LoadFile(ofn.lpstrFile);

我们调用GetOpenFileName()函数显示“打开文件”对话框。 如果单击“打开”按钮,该函数将返回TRUE,然后调用用户定义的LoadFile()函数。

LoadFile()函数内部,我们读取文件并将文件内容放入编辑控件中。 我们创建一个文件句柄。 比我们找出文件大小。 为文件内容分配动态内存。 将内容读入内存,然后将其放入编辑控件。 要将内容放入编辑控件,我们调用SetWindowText()函数。 我们一定不要忘记关闭文件句柄并释放动态内存。

Openfile dialog box

图:打开文件对话框

在 Windows API 教程的这一部分中,我们使用了对话框。

创建自定义小部件

原文: http://zetcode.com/wxpython/customwidgets/

工具箱通常仅提供最常见的窗口小部件,例如按钮,文本窗口小部件,滚动条,滑块等。没有工具箱可以提供所有可能的窗口小部件。 wxPython 有许多小部件; 客户程序员创建了更多专门的小部件。

自定义窗口小部件有两种创建方式:要么修改或增强现有窗口小部件,要么从头开始创建自定义窗口小部件。

超链接小部件

第一个示例将创建一个超链接。 超链接小部件将基于现有的wx.lib.stattext.GenStaticText小部件。

hyperlink.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a Hyperlink widget.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
from wx.lib.stattext import GenStaticText
import webbrowser

class Link(GenStaticText):

    def __init__(self, *args, **kw):
        super(Link, self).__init__(*args, **kw)

        self.font1 = wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, True, 'Verdana')
        self.font2 = wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana')

        self.SetFont(self.font2)
        self.SetForegroundColour('#0000ff')

        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouseEvent)
        self.Bind(wx.EVT_MOTION, self.OnMouseEvent)

    def SetUrl(self, url):

        self.url = url

    def OnMouseEvent(self, e):

        if e.Moving():

            self.SetCursor(wx.Cursor(wx.CURSOR_HAND))
            self.SetFont(self.font1)

        elif e.LeftUp():

            webbrowser.open_new(self.url)

        else:
            self.SetCursor(wx.NullCursor)
            self.SetFont(self.font2)

        e.Skip()

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        panel = wx.Panel(self)

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)

        st = GenStaticText(panel, label='Go to web site:')
        st.SetFont(wx.Font(11, wx.SWISS, wx.NORMAL, wx.BOLD, False, 'Verdana'))
        hbox.Add(st, flag=wx.LEFT, border=20)

        link_wid = Link(panel, label='ZetCode')
        link_wid.SetUrl('http://www.zetcode.com')
        hbox.Add(link_wid, flag=wx.LEFT, border=20)

        vbox.Add(hbox, flag=wx.TOP, border=30)
        panel.SetSizer(vbox)

        self.SetTitle('A Hyperlink')
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

此超链接窗口小部件基于现有的窗口小部件。 在此示例中,我们不绘制任何内容,仅使用现有的小部件,对其进行了一些修改。

from wx.lib.stattext import GenStaticText
import webbrowser

在这里,我们导入基础窗口小部件,从中可以导出我们的超链接窗口小部件和webbrowser模块。 webbrowser模块是标准的 Python 模块。 我们将使用它在默认浏览器中打开链接。

self.SetFont(self.font2)
self.SetForegroundColour('#0000ff')

创建超链接窗口小部件的想法很简单。 我们从基础wx.lib.stattext.GenStaticText小部件类继承。 因此,我们有一个文本小部件。 然后我们对其进行一些修改。 我们更改字体和文本的颜色。

if e.Moving():

    self.SetCursor(wx.Cursor(wx.CURSOR_HAND))
    self.SetFont(self.font1)

如果将鼠标指针悬停在链接上,则会将字体更改为带下划线的字体,还将鼠标指针更改为手形光标。

elif e.LeftUp():

    webbrowser.open_new(self.url)

如果我们左键单击该链接,则会在默认浏览器中打开该链接。

Hyperlink widget

图:一个超链接小部件

刻录小部件

这是我们从头开始创建的小部件的示例。 我们在窗口底部放置一个wx.Panel并手动绘制整个窗口小部件。 如果您曾经刻录过 CD 或 DVD,那么您已经看到了这种小部件。

burning.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a Burning widget.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Burning(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent, size=(-1, 30), style=wx.SUNKEN_BORDER)

        self.parent = parent
        self.font = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL,
            wx.FONTWEIGHT_NORMAL, False, 'Courier 10 Pitch')

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_SIZE, self.OnSize)

    def OnPaint(self, e):

        num = range(75, 700, 75)
        dc = wx.PaintDC(self)
        dc.SetFont(self.font)
        w, h = self.GetSize()

        self.cw = self.parent.GetParent().cw

        step = int(round(w / 10.0))

        j = 0

        till = (w / 750.0) * self.cw
        full = (w / 750.0) * 700

        if self.cw >= 700:

            dc.SetPen(wx.Pen('#FFFFB8'))
            dc.SetBrush(wx.Brush('#FFFFB8'))
            dc.DrawRectangle(0, 0, full, 30)
            dc.SetPen(wx.Pen('#ffafaf'))
            dc.SetBrush(wx.Brush('#ffafaf'))
            dc.DrawRectangle(full, 0, till-full, 30)
        else:

            dc.SetPen(wx.Pen('#FFFFB8'))
            dc.SetBrush(wx.Brush('#FFFFB8'))
            dc.DrawRectangle(0, 0, till, 30)

        dc.SetPen(wx.Pen('#5C5142'))

        for i in range(step, 10*step, step):

            dc.DrawLine(i, 0, i, 6)
            width, height = dc.GetTextExtent(str(num[j]))
            dc.DrawText(str(num[j]), i-width/2, 8)
            j = j + 1

    def OnSize(self, e):

        self.Refresh()

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        self.cw = 75

        panel = wx.Panel(self)
        CenterPanel = wx.Panel(panel)

        self.sld = wx.Slider(CenterPanel, value=75, maxValue=750, size=(200, -1),
            style=wx.SL_LABELS)

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)
        hbox3 = wx.BoxSizer(wx.HORIZONTAL)

        self.wid = Burning(panel)
        hbox.Add(self.wid, 1, wx.EXPAND)

        hbox2.Add(CenterPanel, 1, wx.EXPAND)
        hbox3.Add(self.sld, 0, wx.LEFT|wx.TOP, 35)

        CenterPanel.SetSizer(hbox3)

        vbox.Add(hbox2, 1, wx.EXPAND)
        vbox.Add(hbox, 0, wx.EXPAND)

        self.Bind(wx.EVT_SCROLL, self.OnScroll)

        panel.SetSizer(vbox)

        self.sld.SetFocus()

        self.SetTitle("Burning widget")
        self.Centre()

    def OnScroll(self, e):

        self.cw = self.sld.GetValue()
        self.wid.Refresh()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

此小部件以图形方式显示了介质的总容量和可供我们使用的可用空间。 小部件由滑块控制。 自定义窗口小部件的最小值为 0,最大值为 750。如果值达到 700,则开始绘制红色。 这通常表示过度燃烧。

w, h = self.GetSize()
self.cw = self.parent.GetParent().cw
...
till = (w / 750.0) * self.cw
full = (w / 750.0) * 700

我们动态绘制小部件。 窗口越大,刻录小部件越大。 反之亦然。 这就是为什么我们必须计算在其上绘制自定义窗口小部件的wx.Panel的大小。 till参数确定要绘制的总大小。 该值来自滑块小部件。 它占整个面积的一部分。 full参数确定了我们开始绘制红色的点。 注意使用浮点算法。 这是为了达到更高的精度。

实际图纸包括三个步骤。 我们绘制黄色或红色和黄色矩形。 然后,我们绘制垂直线,这些垂直线将小部件分为几个部分。 最后,我们画出数字来表示介质的容量。

def OnSize(self, e):

    self.Refresh()

每次调整窗口大小时,我们都会刷新小部件。 这将导致小部件重新绘制自身。

def OnScroll(self, e):

    self.cw = self.sld.GetValue()
    self.wid.Refresh()

如果滚动滑块的拇指,我们将获得实际值并将其保存到self.cw参数中。 绘制刻录窗口小部件时使用此值。 然后,我们使小部件重新绘制。

Burning widget

图:刻录小部件

CPU 小部件

有一些系统应用可以测量系统资源,例如温度,内存或 CPU 消耗。 创建专用的小部件以使应用更具吸引力。

以下窗口小部件通常在系统应用中使用。

cpu.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a CPU widget.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class CPU(wx.Panel):

    def __init__(self, parent):
        wx.Panel.__init__(self, parent, size=(80, 110))

        self.parent = parent
        self.SetBackgroundColour('#000000')
        self.Bind(wx.EVT_PAINT, self.OnPaint)

    def OnPaint(self, e):

        dc = wx.PaintDC(self)

        dc.SetDeviceOrigin(0, 100)
        dc.SetAxisOrientation(True, True)

        pos = self.parent.GetParent().GetParent().sel
        rect = pos / 5

        for i in range(1, 21):

            if i > rect:

                dc.SetBrush(wx.Brush('#075100'))
                dc.DrawRectangle(10, i*4, 30, 5)
                dc.DrawRectangle(41, i*4, 30, 5)

            else:
                dc.SetBrush(wx.Brush('#36ff27'))
                dc.DrawRectangle(10, i*4, 30, 5)
                dc.DrawRectangle(41, i*4, 30, 5)

class Example(wx.Frame):

    def __init__(self, *args, **kwargs):
        super(Example, self).__init__(*args, **kwargs)

        self.InitUI()

    def InitUI(self):

        self.sel = 0

        panel = wx.Panel(self)
        centerPanel = wx.Panel(panel)

        self.cpu = CPU(centerPanel)

        hbox = wx.BoxSizer(wx.HORIZONTAL)

        self.slider = wx.Slider(panel, value=self.sel, maxValue=100, size=(-1, 100),
		      style=wx.VERTICAL | wx.SL_INVERSE)
        self.slider.SetFocus()

        hbox.Add(centerPanel, 0,  wx.LEFT | wx.TOP, 20)
        hbox.Add(self.slider, 0, wx.LEFT | wx.TOP, 30)

        self.Bind(wx.EVT_SCROLL, self.OnScroll)

        panel.SetSizer(hbox)

        self.SetTitle("CPU")
        self.Centre()

    def OnScroll(self, e):

        self.sel = e.GetInt()
        self.cpu.Refresh()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

我们创建一个黑色面板。 然后,我们在此面板上绘制小矩形。 矩形的颜色取决于滑块的值。 颜色可以是深绿色或亮绿色。

dc.SetDeviceOrigin(0, 100)
dc.SetAxisOrientation(True, True)

在这里,我们将默认坐标系更改为笛卡尔坐标。 这是为了使图形直观。

pos = self.parent.GetParent().GetParent().sel
rect = pos / 5

在这里,我们获得了大小调整器的值。 每列有 20 个矩形。 滑块有 100 个数字。 rect参数将滑块值转换为以鲜绿色绘制的矩形。

for i in range(1, 21):

    if i > rect:
        dc.SetBrush(wx.Brush('#075100'))
        dc.DrawRectangle(10, i*4, 30, 5)
        dc.DrawRectangle(41, i*4, 30, 5)

    else:
        dc.SetBrush(wx.Brush('#36ff27'))
        dc.DrawRectangle(10, i*4, 30, 5)
        dc.DrawRectangle(41, i*4, 30, 5)

这里我们绘制 40 个矩形,每列 20 个。 如果要绘制的矩形的数目大于转换后的rect值,则将其绘制为深绿色;否则,将其绘制为深绿色。 否则为亮绿色。

CPU widget

图:CPU小部件

在本章中,我们在 wxPython 中创建了自定义窗口小部件。

wxPython 中的应用框架

原文: http://zetcode.com/wxpython/skeletons/

在本节中,我们将创建一些应用框架。 我们的脚本将制定出接口,但不会实现该功能。 目的是展示如何在 wxPython 中完成几个众所周知的 GUI 界面。

文件管理器

文件监视器是文件管理器的骨架。 它复制了 Krusader 的监视,Krusader 是 Unix 系统上可用的文件管理器。 如果我们双击拆分器小部件,它将把文件监视器分成宽度相同的两个部分。 如果我们调整主窗口的大小,也会发生同样的情况。

file_hunter.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a skeleton
of a file manager UI.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
import os
import time

ID_BUTTON=100
ID_EXIT=200
ID_SPLITTER=300

class MyListCtrl(wx.ListCtrl):

    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT)

        images = ['images/empty.png', 'images/folder.png', 'images/source-py.png',
		      'images/image.png', 'images/pdf.png', 'images/up16.png']

        self.InsertColumn(0, 'Name')
        self.InsertColumn(1, 'Ext')
        self.InsertColumn(2, 'Size', wx.LIST_FORMAT_RIGHT)
        self.InsertColumn(3, 'Modified')

        self.SetColumnWidth(0, 220)
        self.SetColumnWidth(1, 70)
        self.SetColumnWidth(2, 100)
        self.SetColumnWidth(3, 420)

        self.il = wx.ImageList(16, 16)

        for i in images:

            self.il.Add(wx.Bitmap(i))

        self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)

        j = 1

        self.InsertItem(0, '..')
        self.SetItemImage(0, 5)

        files = os.listdir('.')

        for i in files:

            (name, ext) = os.path.splitext(i)
            ex = ext[1:]
            size = os.path.getsize(i)
            sec = os.path.getmtime(i)

            self.InsertItem(j, name)
            self.SetItem(j, 1, ex)
            self.SetItem(j, 2, str(size) + ' B')
            self.SetItem(j, 3, time.strftime('%Y-%m-%d %H:%M', time.localtime(sec)))

            if os.path.isdir(i):
                self.SetItemImage(j, 1)
            elif ex == 'py':
                self.SetItemImage(j, 2)
            elif ex == 'jpg':
                self.SetItemImage(j, 3)
            elif ex == 'pdf':
                self.SetItemImage(j, 4)
            else:
                self.SetItemImage(j, 0)

            if (j % 2) == 0:

                self.SetItemBackgroundColour(j, '#e6f1f5')

            j = j + 1

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.splitter = wx.SplitterWindow(self, ID_SPLITTER, style=wx.SP_BORDER)
        self.splitter.SetMinimumPaneSize(50)

        p1 = MyListCtrl(self.splitter)
        p2 = MyListCtrl(self.splitter)
        self.splitter.SplitVertically(p1, p2)

        self.Bind(wx.EVT_SIZE, self.OnSize)
        self.Bind(wx.EVT_SPLITTER_DCLICK, self.OnDoubleClick, id=ID_SPLITTER)

        filemenu= wx.Menu()
        filemenu.Append(ID_EXIT, "E&xit"," Terminate the program")
        editmenu = wx.Menu()
        netmenu = wx.Menu()
        showmenu = wx.Menu()
        configmenu = wx.Menu()
        helpmenu = wx.Menu()

        menuBar = wx.MenuBar()
        menuBar.Append(filemenu, "&File")
        menuBar.Append(editmenu, "&Edit")
        menuBar.Append(netmenu, "&Net")
        menuBar.Append(showmenu, "&Show")
        menuBar.Append(configmenu, "&Config")
        menuBar.Append(helpmenu, "&Help")
        self.SetMenuBar(menuBar)
        self.Bind(wx.EVT_MENU, self.OnExit, id=ID_EXIT)

        tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER |
		      wx.TB_FLAT)

        tb.AddTool(10, 'Previous', wx.Bitmap('images/previous.png'), shortHelp='Previous')
        tb.AddTool(20, 'Up', wx.Bitmap('images/up.png'), shortHelp='Up one directory')
        tb.AddTool(30, 'Home', wx.Bitmap('images/home.png'), shortHelp='Home')
        tb.AddTool(40, 'Refresh', wx.Bitmap('images/refresh.png'), shortHelp='Refresh')
        tb.AddSeparator()
        tb.AddTool(50, 'Edit text', wx.Bitmap('images/textedit.png'), shortHelp='Edit text')
        tb.AddTool(60, 'Terminal', wx.Bitmap('images/terminal.png'), shortHelp='Terminal')
        tb.AddSeparator()
        tb.AddTool(70, 'Help', wx.Bitmap('images/help.png'), shortHelp='Show help')
        tb.Realize()

        self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)

        button1 = wx.Button(self, ID_BUTTON + 1, "F3 View")
        button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit")
        button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy")
        button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move")
        button5 = wx.Button(self, ID_BUTTON + 5, "F7 Mkdir")
        button6 = wx.Button(self, ID_BUTTON + 6, "F8 Delete")
        button7 = wx.Button(self, ID_BUTTON + 7, "F9 Rename")
        button8 = wx.Button(self, ID_EXIT, "F10 Quit")

        self.sizer2.Add(button1, 1, wx.EXPAND)
        self.sizer2.Add(button2, 1, wx.EXPAND)
        self.sizer2.Add(button3, 1, wx.EXPAND)
        self.sizer2.Add(button4, 1, wx.EXPAND)
        self.sizer2.Add(button5, 1, wx.EXPAND)
        self.sizer2.Add(button6, 1, wx.EXPAND)
        self.sizer2.Add(button7, 1, wx.EXPAND)
        self.sizer2.Add(button8, 1, wx.EXPAND)

        self.Bind(wx.EVT_BUTTON, self.OnExit, id=ID_EXIT)

        self.sizer = wx.BoxSizer(wx.VERTICAL)
        self.sizer.Add(self.splitter,1,wx.EXPAND)
        self.sizer.Add(self.sizer2,0,wx.EXPAND)
        self.SetSizer(self.sizer)

        # size = wx.DisplaySize()
        # self.SetSize(size)

        sb = self.CreateStatusBar()
        sb.SetStatusText(os.getcwd())

        self.SetTitle("File Hunter")
        self.Center()

    def OnExit(self, e):

        self.Close(True)

    def OnSize(self, e):

        size = self.GetSize()
        self.splitter.SetSashPosition(size.x / 2)

        e.Skip()

    def OnDoubleClick(self, e):

        size =  self.GetSize()
        self.splitter.SetSashPosition(size.x / 2)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该示例创建一个两面板文件管理器的 UI。

class MyListCtrl(wx.ListCtrl):

    def __init__(self, parent):
        wx.ListCtrl.__init__(self, parent, style=wx.LC_REPORT)

应用的主要区域被wx.ListCtrl小部件占据。

self.il = wx.ImageList(16, 16)

for i in images:

    self.il.Add(wx.Bitmap(i))

self.SetImageList(self.il, wx.IMAGE_LIST_SMALL)

列表控件包含一个列表图像,用于指示文件类型。

files = os.listdir('.')

for i in files:

    (name, ext) = os.path.splitext(i)
    ex = ext[1:]
    size = os.path.getsize(i)
    sec = os.path.getmtime(i)
    ...

我们获取当前工作目录的内容,并确定文件扩展名,大小和最后修改时间。

if os.path.isdir(i):
    self.SetItemImage(j, 1)
elif ex == 'py':
    self.SetItemImage(j, 2)
elif ex == 'jpg':
    self.SetItemImage(j, 3)
elif ex == 'pdf':
    self.SetItemImage(j, 4)
else:
    self.SetItemImage(j, 0)

根据文件扩展名选择文件的图像。

self.splitter = wx.SplitterWindow(self, ID_SPLITTER, style=wx.SP_BORDER)
self.splitter.SetMinimumPaneSize(50)

p1 = MyListCtrl(self.splitter)
p2 = MyListCtrl(self.splitter)
self.splitter.SplitVertically(p1, p2)

我们有两个列表控件,这些控件由拆分器小部件垂直拆分。

menuBar = wx.MenuBar()
menuBar.Append(filemenu, "&File")
menuBar.Append(editmenu, "&Edit")
...

我们有一个菜单栏。

tb = self.CreateToolBar( wx.TB_HORIZONTAL | wx.NO_BORDER |
      wx.TB_FLAT)

tb.AddTool(10, 'Previous', wx.Bitmap('images/previous.png'), shortHelp='Previous')
tb.AddTool(20, 'Up', wx.Bitmap('images/up.png'), shortHelp='Up one directory')
...

我们有一个工具栏。

self.sizer2 = wx.BoxSizer(wx.HORIZONTAL)

button1 = wx.Button(self, ID_BUTTON + 1, "F3 View")
button2 = wx.Button(self, ID_BUTTON + 2, "F4 Edit")
button3 = wx.Button(self, ID_BUTTON + 3, "F5 Copy")
button4 = wx.Button(self, ID_BUTTON + 4, "F6 Move")
...

八个按钮放置在水平大小调整器中,该大小调整器添加到窗口底部。

File manager

图:文件管理器

电子表格

下面的示例创建电子表格应用的 UI。

spreadsheet.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a SpreadSheet UI.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

from wx.lib import sheet
import wx

class MySheet(wx.grid.Grid):

    def __init__(self, *args, **kw):
        super(MySheet, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        nOfRows = 55
        nOfCols = 25

        self.row = self.col = 0
        self.CreateGrid(nOfRows, nOfCols)

        self.SetColLabelSize(20)
        self.SetRowLabelSize(50)

        self.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.OnGridSelectCell)

        for i in range(nOfRows):
            self.SetRowSize(i, 20)

        for i in range(nOfCols):
            self.SetColSize(i, 75)

    def OnGridSelectCell(self, e):

        self.row, self.col = e.GetRow(), e.GetCol()

        control = self.GetParent().GetParent().position
        value =  self.GetColLabelValue(self.col) + self.GetRowLabelValue(self.row)
        control.SetValue(value)

        e.Skip()

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        fonts = ['Times New Roman', 'Times', 'Courier', 'Courier New', 'Helvetica',
                'Sans', 'verdana', 'utkal', 'aakar', 'Arial']
        font_sizes = ['10', '11', '12', '14', '16']

        box = wx.BoxSizer(wx.VERTICAL)
        menuBar = wx.MenuBar()

        menu1 = wx.Menu()
        menuBar.Append(menu1, '&File')
        menu2 = wx.Menu()
        menuBar.Append(menu2, '&Edit')
        menu3 = wx.Menu()
        menuBar.Append(menu3, '&Edit')
        menu4 = wx.Menu()
        menuBar.Append(menu4, '&Insert')
        menu5 = wx.Menu()
        menuBar.Append(menu5, 'F&ormat')
        menu6 = wx.Menu()
        menuBar.Append(menu6, '&Tools')
        menu7 = wx.Menu()
        menuBar.Append(menu7, '&Data')
        menu8 = wx.Menu()
        menuBar.Append(menu8, '&Help')

        self.SetMenuBar(menuBar)

        toolbar1 = wx.ToolBar(self, style= wx.TB_HORIZONTAL)

        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/new.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/open.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/save.png'))

        toolbar1.AddSeparator()

        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/cut.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/copy.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/paste.png'))
        toolbar1.AddTool(wx.ID_ANY, '',  wx.Bitmap('images/delete.png'))

        toolbar1.AddSeparator()

        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/undo.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/redo.png'))

        toolbar1.AddSeparator()

        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/asc.png'))
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/desc.png'))

        toolbar1.AddSeparator()
        toolbar1.AddTool(wx.ID_ANY, '', wx.Bitmap('images/chart.png'))

        toolbar1.AddSeparator()
        toolbar1.AddTool(wx.ID_ANY, '',  wx.Bitmap('images/exit.png'))

        toolbar1.Realize()

        toolbar2 = wx.ToolBar(self, wx.TB_HORIZONTAL | wx.TB_TEXT)

        self.position = wx.TextCtrl(toolbar2)

        font = wx.ComboBox(toolbar2, value='Times', choices=fonts, size=(100, -1),
                style=wx.CB_DROPDOWN)

        font_height = wx.ComboBox(toolbar2, value='10', choices=font_sizes,
                size=(50, -1), style=wx.CB_DROPDOWN)

        toolbar2.AddControl(self.position)
        toolbar2.AddControl(font)
        toolbar2.AddControl(font_height)

        toolbar2.AddSeparator()

        toolbar2.AddCheckTool(wx.ID_ANY, '', wx.Bitmap('images/text-bold.png'))
        toolbar2.AddCheckTool(wx.ID_ANY, '', wx.Bitmap('images/text-italic.png'))
        toolbar2.AddCheckTool(wx.ID_ANY, '', wx.Bitmap('images/text-underline.png'))

        toolbar2.AddSeparator()

        toolbar2.AddTool(wx.ID_ANY, '', wx.Bitmap('images/align-left.png'))
        toolbar2.AddTool(wx.ID_ANY, '', wx.Bitmap('images/align-center.png'))
        toolbar2.AddTool(wx.ID_ANY, '', wx.Bitmap('images/align-right.png'))

        box.Add(toolbar1, border=5)
        box.Add((5,5) , 0)
        box.Add(toolbar2)
        box.Add((5,10) , 0)

        toolbar2.Realize()
        self.SetSizer(box)

        notebook = wx.Notebook(self, style=wx.RIGHT)

        sheet1 = MySheet(notebook)
        sheet2 = MySheet(notebook)
        sheet3 = MySheet(notebook)
        sheet1.SetFocus()

        notebook.AddPage(sheet1, 'Sheet1')
        notebook.AddPage(sheet2, 'Sheet2')
        notebook.AddPage(sheet3, 'Sheet3')

        box.Add(notebook, 1, wx.EXPAND)

        self.CreateStatusBar()

        self.SetSize((550, 550))
        self.SetTitle("SpreadSheet")
        self.Centre()

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

该代码示例创建电子表格应用的 UI。 我有一个菜单栏,工具栏和一个中央网格小部件。

class MySheet(wx.grid.Grid):

    def __init__(self, *args, **kw):
        super(MySheet, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        nOfRows = 55
        nOfCols = 25

        self.row = self.col = 0
        self.CreateGrid(nOfRows, nOfCols)
        ...

我们创建一个自定义的wx.grid.Grid小部件。 我们的每个工作表将有 55 行和 25 列。 用CreateGrid()方法创建一个单元格网格。

control = self.GetParent().GetParent().position

位置文本控件显示了网格小部件的选定单元格。 它是第二个工具栏的第一个小部件。 在Example类内部,我们需要获取对文本控件的引用,该引用在Example类中定义。 MySheet是笔记本的子项。 笔记本是Example的子项。 因此,通过两次调用GetParent()方法,我们设法到达了位置文本控件。

notebook = wx.Notebook(self, style=wx.RIGHT)

sheet1 = MySheet(notebook)
sheet2 = MySheet(notebook)
sheet3 = MySheet(notebook)
sheet1.SetFocus()

notebook.AddPage(sheet1, 'Sheet1')
notebook.AddPage(sheet2, 'Sheet2')
notebook.AddPage(sheet3, 'Sheet3')

将创建三张纸并将其放置在笔记本小部件中。

Spreadsheet

图:电子表格

播放器

以下示例是典型视频播放器的框架。

player.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a Player UI.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.CreateMenuBar()

        panel = wx.Panel(self)

        pnl1 = wx.Panel(self)
        pnl1.SetBackgroundColour(wx.BLACK)
        pnl2 = wx.Panel(self)

        slider1 = wx.Slider(pnl2, value=18, minValue=0, maxValue=1000)
        pause = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/pause.png'))
        play  = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/play.png'))
        forw  = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/forw.png'))
        back  = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/back.png'))
        vol = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/volume.png'))
        slider2 = wx.Slider(pnl2, value=1, minValue=0, maxValue=100,
            size=(120, -1))

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)

        hbox1.Add(slider1, proportion=1)
        hbox2.Add(pause)
        hbox2.Add(play, flag=wx.RIGHT, border=5)
        hbox2.Add(forw, flag=wx.LEFT, border=5)
        hbox2.Add(back)
        hbox2.Add((-1, -1), proportion=1)
        hbox2.Add(vol)
        hbox2.Add(slider2, flag=wx.TOP|wx.LEFT, border=5)

        vbox.Add(hbox1, flag=wx.EXPAND|wx.BOTTOM, border=10)
        vbox.Add(hbox2, proportion=1, flag=wx.EXPAND)
        pnl2.SetSizer(vbox)

        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(pnl1, proportion=1, flag=wx.EXPAND)
        sizer.Add(pnl2, flag=wx.EXPAND|wx.BOTTOM|wx.TOP, border=10)

        self.SetMinSize((350, 300))
        self.CreateStatusBar()
        self.SetSizer(sizer)

        self.SetSize((350, 200))
        self.SetTitle('Player')
        self.Centre()

    def CreateMenuBar(self):

        menubar = wx.MenuBar()
        filem = wx.Menu()
        play = wx.Menu()
        view = wx.Menu()
        tools = wx.Menu()
        favorites = wx.Menu()
        help = wx.Menu()

        filem.Append(wx.ID_ANY, '&Quit', 'Quit application')

        menubar.Append(filem, '&File')
        menubar.Append(play, '&Play')
        menubar.Append(view, '&View')
        menubar.Append(tools, '&Tools')
        menubar.Append(favorites, 'F&avorites')
        menubar.Append(help, '&Help')

        self.SetMenuBar(menubar)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()  

为了构建界面,我们使用了位图按钮,滑块,面板和菜单栏。

pnl1 = wx.Panel(self)
pnl1.SetBackgroundColour(wx.BLACK)

应用的主要区域由黑色背景的面板占据。

slider1 = wx.Slider(pnl2, value=18, minValue=0, maxValue=1000)

wx.Slider用于显示胶片的进度。

pause = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/pause.png'))
play  = wx.BitmapButton(pnl2, bitmap=wx.Bitmap('images/play.png'))

位图按钮用于控制按钮。

self.SetMinSize((350, 300))

在这里,我们设置播放器的最小大小。 将窗口缩小到某个值以下没有太大意义。

Player

图:播放器

浏览器

在下面的示例中,我们模仿经典浏览器 UI 的外观。

browser.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This program creates a browser UI.

author: Jan Bodnar
website: zetcode.com
last edited: May 2018
"""

import wx
from wx.lib.buttons import GenBitmapTextButton

class Example(wx.Frame):

    def __init__(self, *args, **kw):
        super(Example, self).__init__(*args, **kw)

        self.InitUI()

    def InitUI(self):

        self.CreateMenuBar()

        panel = wx.Panel(self)
        # panel.SetBackgroundColour('white')

        vbox = wx.BoxSizer(wx.VERTICAL)
        hbox1 = wx.BoxSizer(wx.HORIZONTAL)
        hbox2 = wx.BoxSizer(wx.HORIZONTAL)

        line1 = wx.StaticLine(panel)
        vbox.Add(line1, 0, wx.EXPAND)

        toolbar1 = wx.Panel(panel, size=(-1, 30))

        back = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/back.png'),
                style=wx.NO_BORDER)
        forward = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/forw.png'),
                style=wx.NO_BORDER)
        refresh = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/refresh.png'),
                style=wx.NO_BORDER)
        stop = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/stop.png'),
                style=wx.NO_BORDER)
        home = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/home.png'),
                style=wx.NO_BORDER)
        address = wx.ComboBox(toolbar1, size=(50, -1))
        go = wx.BitmapButton(toolbar1, bitmap=wx.Bitmap('images/play.png'),
                style=wx.NO_BORDER)
        text = wx.TextCtrl(toolbar1, size=(150, -1))

        hbox1.Add(back)
        hbox1.Add(forward)
        hbox1.Add(refresh)
        hbox1.Add(stop)
        hbox1.Add(home)
        hbox1.Add(address, 1, wx.TOP, 3)
        hbox1.Add(go, 0, wx.TOP | wx.LEFT, 3)
        hbox1.Add(text, 0, wx.TOP | wx.RIGHT, 3)

        toolbar1.SetSizer(hbox1)
        vbox.Add(toolbar1, 0, wx.EXPAND)
        line = wx.StaticLine(panel)
        vbox.Add(line, 0, wx.EXPAND)

        toolbar2 = wx.Panel(panel, size=(-1, 30))
        bookmark1 = wx.BitmapButton(toolbar2, bitmap=wx.Bitmap('images/love.png'),
                style=wx.NO_BORDER)
        bookmark2 = wx.BitmapButton(toolbar2, bitmap=wx.Bitmap('images/book.png'),
                style=wx.NO_BORDER)
        bookmark3 = wx.BitmapButton(toolbar2, bitmap=wx.Bitmap('images/sound.png'),
                style=wx.NO_BORDER)

        hbox2.Add(bookmark1, flag=wx.RIGHT, border=5)
        hbox2.Add(bookmark2, flag=wx.RIGHT, border=5)
        hbox2.Add(bookmark3)

        toolbar2.SetSizer(hbox2)
        vbox.Add(toolbar2, 0, wx.EXPAND)

        line2 = wx.StaticLine(panel)
        vbox.Add(line2, 0, wx.EXPAND)

        panel.SetSizer(vbox)

        self.CreateStatusBar()

        self.SetTitle("Browser")
        self.Centre()

    def CreateMenuBar(self):

        menubar = wx.MenuBar()
        file = wx.Menu()
        file.Append(wx.ID_ANY, '&Quit', '')
        edit = wx.Menu()
        view = wx.Menu()
        go = wx.Menu()
        bookmarks = wx.Menu()
        tools = wx.Menu()
        help = wx.Menu()

        menubar.Append(file, '&File')
        menubar.Append(edit, '&Edit')
        menubar.Append(view, '&View')
        menubar.Append(go, '&Go')
        menubar.Append(bookmarks, '&Bookmarks')
        menubar.Append(tools, '&Tools')
        menubar.Append(help, '&Help')

        self.SetMenuBar(menubar)

def main():

    app = wx.App()
    ex = Example(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

为了创建一个较大的组合框,我们不能使用wx.Toolbar。 我们基于wx.Panel创建自定义工具栏。

toolbar1 = wx.Panel(panel, size=(-1, 40))

我们创建一个普通的wx.Panel

hbox1 = wx.BoxSizer(wx.HORIZONTAL)
...
hbox1.Add(back)
hbox1.Add(forward)
hbox1.Add(refresh)

我们创建一个水平大小调整器,并添加所有必要的按钮。

hbox1.Add(address, 1, wx.TOP, 4)

然后,将组合框添加到大小调整器。 这种组合框通常称为地址栏。 请注意,这是唯一将比例设置为 1 的小部件。这是使其可调整大小的必要条件。

line2 = wx.StaticLine(panel)
vbox.Add(line2, 0, wx.EXPAND)

工具栏由一行分隔。

Browser UI

图:浏览器 UI

在 wxPython 教程的这一部分中,我们创建了一些应用框架。

wxPython 中的俄罗斯方块游戏

原文: http://zetcode.com/wxpython/thetetrisgame/

俄罗斯方块游戏是有史以来最受欢迎的计算机游戏之一。 原始游戏是由俄罗斯程序员 Alexey Pajitnov 于 1985 年设计和编程的。此后,几乎所有版本的几乎所有计算机平台上都可以使用俄罗斯方块。

俄罗斯方块被称为下降块益智游戏。 在这个游戏中,我们有七个不同的形状,称为 tetrominoes:S 形,Z 形,T 形,L 形,线形,MirroredL 形和方形。 这些形状中的每一个都形成有四个正方形。 形状从板上掉下来。 俄罗斯方块游戏的目的是移动和旋转形状,以使其尽可能地适合。 如果我们设法形成一行,则该行将被破坏并得分。 我们玩俄罗斯方块游戏,直到达到顶峰。

Tetrominoes

图:Tetrominoes

wxPython 是旨在创建应用的工具包。 还有其他一些旨在创建计算机游戏的库。 不过,可以使用 wxPython 和其他应用工具包来创建游戏。

开发

我们的俄罗斯方块游戏没有图像,我们使用 wxPython 中提供的绘图 API 绘制四方块。 每个计算机游戏的背后都有一个数学模型。 俄罗斯方块也是如此。

游戏背后的一些想法:

  • 我们使用wx.Timer创建游戏周期
  • 绘制四方块
  • 形状以正方形为单位移动(不是逐个像素移动)
  • 从数学上讲,棋盘是简单的数字列表

tetris.py

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
ZetCode wxPython tutorial

This is Tetris game clone in wxPython.

author: Jan Bodnar
website: www.zetcode.com
last modified: May 2018
"""

import wx
import random

class Tetris(wx.Frame):

    def __init__(self, parent):
        wx.Frame.__init__(self, parent, size=(180, 380),
            style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER ^ wx.MAXIMIZE_BOX)

        self.initFrame()

    def initFrame(self):

        self.statusbar = self.CreateStatusBar()
        self.statusbar.SetStatusText('0')
        self.board = Board(self)
        self.board.SetFocus()
        self.board.start()

        self.SetTitle("Tetris")
        self.Centre()

class Board(wx.Panel):

    BoardWidth = 10
    BoardHeight = 22
    Speed = 300
    ID_TIMER = 1

    def __init__(self, *args, **kw):

        super(Board, self).__init__(*args, **kw)

        self.initBoard()

    def initBoard(self):

        self.timer = wx.Timer(self, Board.ID_TIMER)
        self.isWaitingAfterLine = False
        self.curPiece = Shape()
        self.nextPiece = Shape()
        self.curX = 0
        self.curY = 0
        self.numLinesRemoved = 0
        self.board = []

        self.isStarted = False
        self.isPaused = False

        self.Bind(wx.EVT_PAINT, self.OnPaint)
        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
        self.Bind(wx.EVT_TIMER, self.OnTimer, id=Board.ID_TIMER)

        self.clearBoard()

    def shapeAt(self, x, y):

        return self.board[(y * Board.BoardWidth) + x]

    def setShapeAt(self, x, y, shape):

        self.board[(y * Board.BoardWidth) + x] = shape

    def squareWidth(self):

        return self.GetClientSize().GetWidth() // Board.BoardWidth

    def squareHeight(self):

        return self.GetClientSize().GetHeight() // Board.BoardHeight

    def start(self):

        if self.isPaused:
            return

        self.isStarted = True
        self.isWaitingAfterLine = False
        self.numLinesRemoved = 0
        self.clearBoard()

        self.newPiece()
        self.timer.Start(Board.Speed)

    def pause(self):

        if not self.isStarted:
            return

        self.isPaused = not self.isPaused
        statusbar = self.GetParent().statusbar

        if self.isPaused:
            self.timer.Stop()
            statusbar.SetStatusText('paused')
        else:
            self.timer.Start(Board.Speed)
            statusbar.SetStatusText(str(self.numLinesRemoved))

        self.Refresh()

    def clearBoard(self):

        for i in range(Board.BoardHeight * Board.BoardWidth):
            self.board.append(Tetrominoes.NoShape)

    def OnPaint(self, event):

        dc = wx.PaintDC(self)

        size = self.GetClientSize()
        boardTop = size.GetHeight() - Board.BoardHeight * self.squareHeight()

        for i in range(Board.BoardHeight):
            for j in range(Board.BoardWidth):

                shape = self.shapeAt(j, Board.BoardHeight - i - 1)

                if shape != Tetrominoes.NoShape:
                    self.drawSquare(dc,
                        0 + j * self.squareWidth(),
                        boardTop + i * self.squareHeight(), shape)

        if self.curPiece.shape() != Tetrominoes.NoShape:

            for i in range(4):

                x = self.curX + self.curPiece.x(i)
                y = self.curY - self.curPiece.y(i)

                self.drawSquare(dc, 0 + x * self.squareWidth(),
                    boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
                    self.curPiece.shape())

    def OnKeyDown(self, event):

        if not self.isStarted or self.curPiece.shape() == Tetrominoes.NoShape:
            event.Skip()
            return

        keycode = event.GetKeyCode()

        if keycode == ord('P') or keycode == ord('p'):
            self.pause()
            return

        if self.isPaused:
            return

        elif keycode == wx.WXK_LEFT:
            self.tryMove(self.curPiece, self.curX - 1, self.curY)

        elif keycode == wx.WXK_RIGHT:
            self.tryMove(self.curPiece, self.curX + 1, self.curY)

        elif keycode == wx.WXK_DOWN:
            self.tryMove(self.curPiece.rotatedRight(), self.curX, self.curY)

        elif keycode == wx.WXK_UP:
            self.tryMove(self.curPiece.rotatedLeft(), self.curX, self.curY)

        elif keycode == wx.WXK_SPACE:
            self.dropDown()

        elif keycode == ord('D') or keycode == ord('d'):
            self.oneLineDown()

        else:
            event.Skip()

    def OnTimer(self, event):

        if event.GetId() == Board.ID_TIMER:

            if self.isWaitingAfterLine:
                self.isWaitingAfterLine = False
                self.newPiece()

            else:
                self.oneLineDown()

        else:
            event.Skip()

    def dropDown(self):

        newY = self.curY

        while newY > 0:
            if not self.tryMove(self.curPiece, self.curX, newY - 1):
                break
            newY -= 1

        self.pieceDropped()

    def oneLineDown(self):

        if not self.tryMove(self.curPiece, self.curX, self.curY - 1):
            self.pieceDropped()

    def pieceDropped(self):

        for i in range(4):

            x = self.curX + self.curPiece.x(i)
            y = self.curY - self.curPiece.y(i)
            self.setShapeAt(x, y, self.curPiece.shape())

        self.removeFullLines()

        if not self.isWaitingAfterLine:
            self.newPiece()

    def removeFullLines(self):

        numFullLines = 0

        statusbar = self.GetParent().statusbar

        rowsToRemove = []

        for i in range(Board.BoardHeight):
            n = 0
            for j in range(Board.BoardWidth):
                if not self.shapeAt(j, i) == Tetrominoes.NoShape:
                    n = n + 1

            if n == 10:
                rowsToRemove.append(i)

        rowsToRemove.reverse()

        for m in rowsToRemove:
            for k in range(m, Board.BoardHeight):
                for l in range(Board.BoardWidth):
                        self.setShapeAt(l, k, self.shapeAt(l, k + 1))

            numFullLines = numFullLines + len(rowsToRemove)

            if numFullLines > 0:

                self.numLinesRemoved = self.numLinesRemoved + numFullLines
                statusbar.SetStatusText(str(self.numLinesRemoved))
                self.isWaitingAfterLine = True
                self.curPiece.setShape(Tetrominoes.NoShape)
                self.Refresh()

    def newPiece(self):

        self.curPiece = self.nextPiece
        statusbar = self.GetParent().statusbar
        self.nextPiece.setRandomShape()

        self.curX = Board.BoardWidth // 2 + 1
        self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

        if not self.tryMove(self.curPiece, self.curX, self.curY):

            self.curPiece.setShape(Tetrominoes.NoShape)
            self.timer.Stop()
            self.isStarted = False
            statusbar.SetStatusText('Game over')

    def tryMove(self, newPiece, newX, newY):

        for i in range(4):

            x = newX + newPiece.x(i)
            y = newY - newPiece.y(i)

            if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
                return False

            if self.shapeAt(x, y) != Tetrominoes.NoShape:
                return False

        self.curPiece = newPiece
        self.curX = newX
        self.curY = newY
        self.Refresh()

        return True

    def drawSquare(self, dc, x, y, shape):

        colors = ['#000000', '#CC6666', '#66CC66', '#6666CC',
                  '#CCCC66', '#CC66CC', '#66CCCC', '#DAAA00']

        light = ['#000000', '#F89FAB', '#79FC79', '#7979FC',
                 '#FCFC79', '#FC79FC', '#79FCFC', '#FCC600']

        dark = ['#000000', '#803C3B', '#3B803B', '#3B3B80',
                 '#80803B', '#803B80', '#3B8080', '#806200']

        pen = wx.Pen(light[shape])
        pen.SetCap(wx.CAP_PROJECTING)
        dc.SetPen(pen)

        dc.DrawLine(x, y + self.squareHeight() - 1, x, y)
        dc.DrawLine(x, y, x + self.squareWidth() - 1, y)

        darkpen = wx.Pen(dark[shape])
        darkpen.SetCap(wx.CAP_PROJECTING)
        dc.SetPen(darkpen)

        dc.DrawLine(x + 1, y + self.squareHeight() - 1,
            x + self.squareWidth() - 1, y + self.squareHeight() - 1)
        dc.DrawLine(x + self.squareWidth() - 1,
        y + self.squareHeight() - 1, x + self.squareWidth() - 1, y + 1)

        dc.SetPen(wx.TRANSPARENT_PEN)
        dc.SetBrush(wx.Brush(colors[shape]))
        dc.DrawRectangle(x + 1, y + 1, self.squareWidth() - 2,
        self.squareHeight() - 2)

class Tetrominoes(object):

    NoShape = 0
    ZShape = 1
    SShape = 2
    LineShape = 3
    TShape = 4
    SquareShape = 5
    LShape = 6
    MirroredLShape = 7

class Shape(object):

    coordsTable = (
        ((0, 0),     (0, 0),     (0, 0),     (0, 0)),
        ((0, -1),    (0, 0),     (-1, 0),    (-1, 1)),
        ((0, -1),    (0, 0),     (1, 0),     (1, 1)),
        ((0, -1),    (0, 0),     (0, 1),     (0, 2)),
        ((-1, 0),    (0, 0),     (1, 0),     (0, 1)),
        ((0, 0),     (1, 0),     (0, 1),     (1, 1)),
        ((-1, -1),   (0, -1),    (0, 0),     (0, 1)),
        ((1, -1),    (0, -1),    (0, 0),     (0, 1))
    )

    def __init__(self):

        self.coords = [[0,0] for i in range(4)]
        self.pieceShape = Tetrominoes.NoShape

        self.setShape(Tetrominoes.NoShape)

    def shape(self):

        return self.pieceShape

    def setShape(self, shape):

        table = Shape.coordsTable[shape]
        for i in range(4):
            for j in range(2):
                self.coords[i][j] = table[i][j]

        self.pieceShape = shape

    def setRandomShape(self):

        self.setShape(random.randint(1, 7))

    def x(self, index):

        return self.coords[index][0]

    def y(self, index):

        return self.coords[index][1]

    def setX(self, index, x):

        self.coords[index][0] = x

    def setY(self, index, y):

        self.coords[index][1] = y

    def minX(self):

        m = self.coords[0][0]
        for i in range(4):
            m = min(m, self.coords[i][0])

        return m

    def maxX(self):

        m = self.coords[0][0]
        for i in range(4):
            m = max(m, self.coords[i][0])

        return m

    def minY(self):

        m = self.coords[0][1]
        for i in range(4):
            m = min(m, self.coords[i][1])

        return m

    def maxY(self):

        m = self.coords[0][1]

        for i in range(4):
            m = max(m, self.coords[i][1])

        return m

    def rotatedLeft(self):

        if self.pieceShape == Tetrominoes.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):
            result.setX(i, self.y(i))
            result.setY(i, -self.x(i))

        return result

    def rotatedRight(self):

        if self.pieceShape == Tetrominoes.SquareShape:
            return self

        result = Shape()
        result.pieceShape = self.pieceShape

        for i in range(4):
            result.setX(i, -self.y(i))
            result.setY(i, self.x(i))

        return result

def main():

    app = wx.App()
    ex = Tetris(None)
    ex.Show()
    app.MainLoop()

if __name__ == '__main__':
    main()

游戏进行了简化,以便于理解。 它在启动应用后立即启动。 我们可以通过按 p 键暂停游戏。 空格键将下降的俄罗斯方块片段立即放到底部。 d 键将棋子下降一行。 (可以用来加快跌落速度。)游戏以恒定速度进行,没有实现加速。 分数是我们已删除的行数。

def __init__(self, *args, **kw):

    super(Board, self).__init__(*args, **kw)

Windows 用户注意事项。 如果无法使用箭头键,则将style=wx.WANTS_CHARS添加到板子构造器中。

...
self.curX = 0
self.curY = 0
self.numLinesRemoved = 0
self.board = []
...

在开始游戏周期之前,我们先初始化一些重要的变量。 self.board变量是一个从 0 到 7 的数字的列表。它表示各种形状的位置以及板上形状的其余部分。

for i in range(Board.BoardHeight):
    for j in range(Board.BoardWidth):

        shape = self.shapeAt(j, Board.BoardHeight - i - 1)

        if shape != Tetrominoes.NoShape:
            self.drawSquare(dc,
                0 + j * self.squareWidth(),
                boardTop + i * self.squareHeight(), shape)

游戏的绘图分为两个步骤。 在第一步中,我们绘制所有形状或已放置到板底部的形状的其余部分。 所有正方形都记在self.board列表变量中。 我们使用shapeAt()方法访问它。

if self.curPiece.shape() != Tetrominoes.NoShape:

    for i in range(4):

        x = self.curX + self.curPiece.x(i)
        y = self.curY - self.curPiece.y(i)

        self.drawSquare(dc, 0 + x * self.squareWidth(),
            boardTop + (Board.BoardHeight - y - 1) * self.squareHeight(),
            self.curPiece.shape())

下一步是绘制掉落的实际零件。

elif keycode == wx.WXK_LEFT:
    self.tryMove(self.curPiece, self.curX - 1, self.curY)

OnKeyDown()方法中,我们检查按键是否按下。 如果按向左箭头键,我们将尝试将棋子向左移动。 我们说尝试,因为该部分可能无法移动。

def tryMove(self, newPiece, newX, newY):

    for i in range(4):
        x = newX + newPiece.x(i)
        y = newY - newPiece.y(i)
        if x < 0 or x >= Board.BoardWidth or y < 0 or y >= Board.BoardHeight:
            return False
        if self.shapeAt(x, y) != Tetrominoes.NoShape:
            return False

    self.curPiece = newPiece
    self.curX = newX
    self.curY = newY
    self.Refresh()

    return True

tryMove()方法中,我们尝试移动形状。 如果形状在板的边缘或与其他零件相邻,则返回False; 否则,我们将当前下降片放到新位置并返回True

def OnTimer(self, event):

    if event.GetId() == Board.ID_TIMER:
        if self.isWaitingAfterLine:
            self.isWaitingAfterLine = False
            self.newPiece()
        else:
            self.oneLineDown()
    else:
        event.Skip()

OnTimer()方法中,我们可以创建一个新的片段,将前一个片段放到底部,或者将下降的片段向下移动一行。

def removeFullLines(self):

    numFullLines = 0

    rowsToRemove = []

    for i in range(Board.BoardHeight):
        n = 0
        for j in range(Board.BoardWidth):
            if not self.shapeAt(j, i) == Tetrominoes.NoShape:
                n = n + 1

        if n == 10:
            rowsToRemove.append(i)

    rowsToRemove.reverse()

    for m in rowsToRemove:
        for k in range(m, Board.BoardHeight):
            for l in range(Board.BoardWidth):
                self.setShapeAt(l, k, self.shapeAt(l, k + 1))
...

如果片段触底,我们将调用removeFullLines()方法。 首先,我们找出所有实线并将其删除。 通过将所有行移动到当前全行上方来将其向下移动一行来实现。 请注意,我们颠倒了要删除的行的顺序。 否则,它将无法正常工作。 在我们的情况下,我们使用朴素重力。 这意味着碎片可能会漂浮在空的间隙上方。

def newPiece(self):

    self.curPiece = self.nextPiece
    statusbar = self.GetParent().statusbar
    self.nextPiece.setRandomShape()
    self.curX = Board.BoardWidth / 2 + 1
    self.curY = Board.BoardHeight - 1 + self.curPiece.minY()

    if not self.tryMove(self.curPiece, self.curX, self.curY):

        self.curPiece.setShape(Tetrominoes.NoShape)
        self.timer.Stop()
        self.isStarted = False
        statusbar.SetStatusText('Game over')

newPiece()方法随机创建一个新的俄罗斯方块。 如果棋子无法进入其初始位置,则游戏结束。

Shape类保存有关俄罗斯方块的信息。

self.coords = [[0,0] for i in range(4)]

创建后,我们将创建一个空坐标列表。 该列表将保存俄罗斯方块的坐标。 例如,元组(0,-1),(0、0),(-1、0),(-1,-1)表示旋转的 S 形。 下图说明了形状。

Coordinates

图:坐标

当绘制当前下降片时,将其绘制在self.curXself.curY position处。 然后,我们查看坐标表并绘制所有四个正方形。

Tetris

图:俄罗斯方块

这是 wxPython 中的俄罗斯方块游戏。

GTK+ 教程

原文: http://zetcode.com/gui/gtk2/

这是 GTK+ 编程指南。 在本教程中,我们将学习 GTK+ 和 C 语言的 GUI 编程基础。 GTK+ 教程适合初学者和中级程序员。 本教程介绍了 GTK+ 2。

目录

  • 简介
  • 首个程序
  • 菜单和工具栏
  • 布局管理
  • 事件
  • 对话框
  • 小部件
  • 小部件 II
  • GtkTreeView小部件
  • GtkTextView小部件
  • 自定义 GTK+ 小部件

GTK+

GTK+ 是用于创建图形用户界面的库。 该库是用 C 编程语言创建的。 GTK+ 也称为 GIMP 工具包。 最初,该库是在开发 GIMP 图像处理器时创建的。

相关教程

GTK# 教程, PyGTK 教程, Ruby GTK 教程和 Cairo 图形教程。

GTK+ 简介

原文: http://zetcode.com/gui/gtk2/introduction/

这是 GTK+ 编程入门教程。 本教程使用 C 编程语言编写。 它已在 Linux 上创建并经过测试。 GTK+ 编程教程适合新手和中级程序员。 本教程介绍了 GTK+ 2。

GTK+

GTK+ 是用于创建图形用户界面的库。 该库是用 C 编程语言创建的。 GTK+ 库也称为 GIMP 工具箱。 最初,该库是在开发 GIMP 图像处理器时创建的。 从那时起,GTK+ 成为 Linux 和 BSD Unix 下最受欢迎的工具包之一。 如今,开源世界中的大多数 GUI 软件都是在 Qt 或 GTK+ 中创建的。 GTK+ 是一个面向对象的应用编程接口。 面向对象的系统是使用 Glib 对象系统创建的,该系统是 GTK+ 库的基础。GObject还可以为各种其他编程语言创建语言绑定。 存在用于 C++ ,Python,Perl,Java,C# 和其他编程语言的语言绑定。

GTK+ 本身取决于以下库:

  • Glib
  • Pango
  • ATK
  • GDK
  • GdkPixbuf
  • Cario

Glib 是通用工具库。 它提供各种数据类型,字符串工具,启用错误报告,消息日志记录,使用线程以及其他有用的编程功能。Pango 是一个实现国际化的库。ATK 是可访问性工具包; 它提供了一些工具,可以帮助残障人士使用计算机。GDK 是基础图形系统提供的低级绘图和窗口功能的包装。 在 Linux 上,GDK 位于 X Server 和 GTK+ 库之间。 它处理基本的渲染,例如图形基元,光栅图形,光标,字体以及窗口事件和拖放功能。GdkPixbuf 库是用于图像加载和像素缓冲区操作的工具包。Cario 是用于创建 2D 向量图形的库。 自 2.8 版起,它已包含在 GTK+ 中。

Gnome 和 XFce 桌面环境已使用 GTK+ 库创建。 SWT 和 wxWidgets 是使用 GTK+ 的众所周知的编程框架。 使用 GTK+ 的著名软件应用包括 Firefox 或 Inkscape。

编译 GTK+ 应用

要编译 GTK+ 应用,我们有一个方便的工具pkg-configpgk-config返回有关已安装库的元数据。 如果我们要使用特定的库,它将为我们提供必要的依赖库,并包含我们需要的文件。 pkg-config程序从特殊的元数据文件中检索有关包的信息。

$ gcc -o simple simple.c `pkg-config --libs --cflags gtk+-2.0`

该行编译一个基本程序。 源代码由一个文件simple.c组成。

$ pkg-config --cflags gtk+-2.0 | xargs -n3
-pthread -I/usr/include/gtk-2.0 -I/usr/lib/x86_64-linux-gnu/gtk-2.0/include
-I/usr/include/atk-1.0 -I/usr/include/cairo -I/usr/include/gdk-pixbuf-2.0
-I/usr/include/pango-1.0 -I/usr/include/gio-unix-2.0/ -I/usr/include/freetype2
-I/usr/include/glib-2.0 -I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/pixman-1
-I/usr/include/libpng12 -I/usr/include/harfbuzz

--cflags参数显示编译 GTK+ 程序所需的预处理器和编译标志,包括所有依赖项的标志。

$ pkg-config --libs gtk+-2.0 | xargs -n5
-lgtk-x11-2.0 -lgdk-x11-2.0 -latk-1.0 -lgio-2.0 -lpangoft2-1.0
-lpangocairo-1.0 -lgdk_pixbuf-2.0 -lcairo -lpango-1.0 -lfontconfig
-lgobject-2.0 -lglib-2.0 -lfreetype

--libs参数列出了必要的库。

版本

以下程序将打印 GTK+ 和 Glib 库的版本。

version.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

    gtk_init(&argc, &argv);

    g_printf("GTK+ version: %d.%d.%d\n", gtk_major_version, 
        gtk_minor_version, gtk_micro_version);
    g_printf("Glib version: %d.%d.%d\n", glib_major_version,
        glib_minor_version, glib_micro_version);    

    return 0;
}

该程序使用内置常量。

$ ./version 
GTK+ version: 2.24.23
Glib version: 2.40.2

这是version程序的输出。

数据来源

  • gtk.org
  • gtkforums.com
  • GTK+ 2 参考

这是 GTK+ 库的简介。

GTK+ 中的第一个程序

原文: http://zetcode.com/gui/gtk2/firstprograms/

在 GTK+ 编程教程的这一部分中,我们在 GTK+ 中创建第一个程序。 我们在屏幕上居中放置一个窗口,在标题栏中显示一个图标,显示一个小的工具提示,并为按钮小部件创建一个助记符。

简单的例子

我们的第一个示例显示了一个基本窗口。

simple.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_widget_show(window);

  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);  

  gtk_main();

  return 0;
}

本示例在屏幕上显示一个基本窗口。

GtkWidget *window;

GtkWidget是 GTK+ 中所有小部件都派生的基类。 它管理小部件的生命周期,状态和样式。

gtk_init(&argc, &argv);

gtk_init()函数初始化 GTK+ 并解析一些标准命令行选项。 在使用任何其他 GTK+ 函数之前,必须先调用此函数。

window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

gtk_window_new()函数创建一个新的GtkWindow,这是一个可以包含其他窗口小部件的顶层窗口。 窗口类型为GTK_WINDOW_TOPLEVEL; 顶层窗口具有标题栏和边框。 它们由窗口管理器管理。

gtk_widget_show(window);

get_widget_show()标记要显示的小部件。 任何未显示的小部件都不会出现在屏幕上。

g_signal_connect(window, "destroy",
    G_CALLBACK(gtk_main_quit), NULL);  

g_signal_connect()函数将回调函数连接到特定对象的信号。 默认情况下,该窗口不响应destroy信号。 我们必须通过将destroy信号连接到内置的gtk_main_quit()函数来显式终止应用,该函数会终止应用。

gtk_main();

该代码进入 GTK+ 主循环。 从这一点开始,应用就坐下来等待事件发生。

$ gcc -o simple simple.c `pkg-config --libs --cflags gtk+-2.0`

这就是我们编译示例的方式。

Simple

图:简单

使窗口居中

如果我们不自己定位窗口,则窗口管理器将为我们定位。 在下一个示例中,我们将窗口居中。

center.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "Center");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_widget_show(window);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_main();

  return 0;
}

在示例中,我们将窗口居中,设置标题,并调整窗口大小。

gtk_window_set_title(GTK_WINDOW(window), "Center");

gtk_window_set_title()函数设置窗口标题。 如果我们自己不设置标题,则 GTK+ 将使用源文件的名称作为标题。

gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);

gtk_window_set_default_size()将窗口的大小设置为230x150。 请注意,我们谈论的是客户区域,不包括窗口管理器提供的装饰。

gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

GTK_WIN_POS_CENTER常量传递给gtk_window_set_position()函数,使程序在屏幕上居中。

应用图标

在下一个示例中,我们显示应用图标。 大多数窗口管理器在标题栏的左上角以及任务栏上都显示图标。

icon.c

#include <gtk/gtk.h>

GdkPixbuf *create_pixbuf(const gchar * filename) {

   GdkPixbuf *pixbuf;
   GError *error = NULL;
   pixbuf = gdk_pixbuf_new_from_file(filename, &error);

   if (!pixbuf) {

      fprintf(stderr, "%s\n", error->message);
      g_error_free(error);
   }

   return pixbuf;
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GdkPixbuf *icon;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "Icon");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  icon = create_pixbuf("web.png");  
  gtk_window_set_icon(GTK_WINDOW(window), icon);

  gtk_widget_show(window);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_object_unref(icon);    

  gtk_main();

  return 0;
}

该代码示例显示了一个应用图标。

pixbuf = gdk_pixbuf_new_from_file(filename, &error);

gdk_pixbuf_new_from_file()函数通过从文件加载图像来创建新的pixbuf。 自动检测文件格式。 如果返回NULL,则将设置错误。

if (!pixbuf) {

    fprintf(stderr, "%s\n", error->message);
    g_error_free(error);
}

如果无法加载该图标,则会显示一条错误消息。

icon = create_pixbuf("web.png");  
gtk_window_set_icon(GTK_WINDOW(window), icon);

gtk_window_set_icon()显示窗口的图标。 create_pixbuf()从 PNG 文件创建GdkPixbuf

g_object_unref(icon);

g_object_unref()减少 pixbuf 对象的引用计数。 当其引用计数降至 0 时,该对象将被终结(即释放其内存)。

Icon

图:图标

工具提示

工具提示是一个小的矩形窗口,它提供有关对象的简短信息。 它通常是一个 GUI 组件。 它是应用帮助系统的一部分。

tooltip.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *halign;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "Tooltip");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  button = gtk_button_new_with_label("Button");
  gtk_widget_set_tooltip_text(button, "Button widget");

  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), button);
  gtk_container_add(GTK_CONTAINER(window), halign);  

  gtk_widget_show_all(window);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);  

  gtk_main();

  return 0;
}

该示例显示了按钮小部件上的基本工具提示。

gtk_container_set_border_width(GTK_CONTAINER(window), 15);

gtk_container_set_border_width()在窗口边缘周围设置一些边框空间。

gtk_widget_set_tooltip_text(button, "Button widget");

gtk_widget_set_tooltip_text()设置给定窗口小部件的基本工具提示。

halign = gtk_alignment_new(0, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(halign), button);

GtkAlignment是一个基本容器,可用于将其子控件与窗口的侧面对齐。 在我们的例子中,该按钮位于窗口的左上角。 该函数的第一个参数是xalignyalignxalign的值为 0 表示左对齐; yalign的值为 0 表示顶部对齐。 第三和第四参数是缩放值。 将两个参数都传递 0 表示小部件不会在两个方向上展开。

gtk_container_add(GTK_CONTAINER(window), halign);

GtkAlignment被设置为窗口的主要容器。

gtk_widget_show_all(window);

当我们处理多个窗口小部件时,在容器上调用gtk_widget_show_all()比单独显示所有窗口小部件容易。 在我们的例子中,窗口和按钮都显示在一个镜头中。

Tooltip

图:工具提示

助记符

助记符是用于激活支持助记符的窗口小部件的快捷键。 它们可以与标签,按钮或菜单项一起使用。 助记符是通过在小部件的标签上添加字符来创建的。 它使下一个字符成为助记符。 字符与无鼠标修饰符(通常为 Alt )结合在一起。 选择的字符带有下划线,但是可以以平台特定的方式强调。 在某些平台上,仅在按下无鼠标修饰符后才对字符加下划线。

mnemonic.c

#include <gtk/gtk.h>

void print_msg(GtkWidget *widget, gpointer window) {

  g_printf("Button clicked\n");
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *button;
  GtkWidget *halign;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "Mnemonic");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  button = gtk_button_new_with_mnemonic("_Button");

  g_signal_connect(button, "clicked", 
      G_CALLBACK(print_msg), NULL);  

  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), button);
  gtk_container_add(GTK_CONTAINER(window), halign);  

  gtk_widget_show_all(window);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL); 

  gtk_main();

  return 0;
}

我们为按钮小部件设置了助记符。 可以使用 Alt + B 键盘快捷键激活。

button = gtk_button_new_with_mnemonic("_Button");

gtk_button_new_with_mnemonic()函数创建一个包含标签的新GtkButton。 如果标签中的字符前面带有下划线,则会在其下划线。

g_signal_connect(button, "clicked", 
    G_CALLBACK(print_msg), NULL); 

当我们触发按钮时,一条消息会打印到控制台上。 通过g_signal_connect()函数,我们将clicked信号连接到print_msg函数。

目前,有三种激活按钮的方式:单击鼠标左键, Alt + B 快捷方式以及空格键 按钮具有焦点)。

Mnemonic

图:助记符

在本章中,我们创建了一些简单的 GTK+ 程序。

GTK+ 中的菜单和工具栏

原文: http://zetcode.com/gui/gtk2/menusandtoolbars/

在 GTK+ 编程教程的这一部分中,我们使用菜单和工具栏。菜单栏是 GUI 应用的常见部分。 它是位于各个菜单中的一组命令。

GtkMenuBar是创建菜单栏的窗口小部件。 它包含一对多GtkMenuItems。 菜单项是用户可以选择的对象。 GtkMenu实现了一个由GtkMenuItem对象列表组成的下拉菜单,用户可以对其进行导航和激活以执行应用功能。 GtkMenu附加到菜单栏的菜单项或另一个菜单的菜单项。

Menus

图:菜单

该图显示了菜单栏及其菜单的结构。

简单菜单示例

在第一个示例中,我们创建带有一个“文件”菜单的菜单栏。

simplemenu.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *fileMenu;
  GtkWidget *fileMi;
  GtkWidget *quitMi;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Simple menu");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();
  fileMenu = gtk_menu_new();

  fileMi = gtk_menu_item_new_with_label("File");
  quitMi = gtk_menu_item_new_with_label("Quit");

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(fileMi), fileMenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), quitMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), fileMi);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(quitMi), "activate",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

示例中的菜单具有一个菜单项。 通过选择项目,应用退出。

menubar = gtk_menu_bar_new();

gtk_menu_bar_new()创建一个新的GtkMenuBar

filemenu = gtk_menu_new();

gtk_menu_new()函数创建一个新的GtkMenu

gtk_menu_item_set_submenu(GTK_MENU_ITEM(fileMi), fileMenu);

通过gtk_menu_item_set_submenu()函数将fileMenu设置为文件菜单项。 菜单是容纳菜单项的容器。 它们本身已插入特定的菜单项。

gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), quitMi);

通过gtk_menu_shell_append()函数将quitMi添加到文件菜单。

gtk_menu_shell_append(GTK_MENU_SHELL(menubar), fileMi);

使用gtk_menu_shell_append()函数将文件菜单项添加到菜单栏。 GtkMenuGtkMenuBar均来自GtkMenuShell

g_signal_connect(G_OBJECT(quitMi), "activate",
    G_CALLBACK(gtk_main_quit), NULL);

通过选择退出菜单项,我们终止了该应用。

Simple menu

图:简单菜单

子菜单

下一个示例演示如何创建子菜单。 子菜单是另一个菜单中的菜单。

submenu.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *fileMenu;
  GtkWidget *imprMenu;
  GtkWidget *sep;
  GtkWidget *fileMi;
  GtkWidget *imprMi;
  GtkWidget *feedMi;
  GtkWidget *bookMi;
  GtkWidget *mailMi;
  GtkWidget *quitMi;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Submenu");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();

  fileMenu = gtk_menu_new();
  fileMi = gtk_menu_item_new_with_label("File");

  imprMenu = gtk_menu_new();
  imprMi = gtk_menu_item_new_with_label("Import");
  feedMi = gtk_menu_item_new_with_label("Import news feed...");
  bookMi = gtk_menu_item_new_with_label("Import bookmarks...");
  mailMi = gtk_menu_item_new_with_label("Import mail...");

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(imprMi), imprMenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(imprMenu), feedMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(imprMenu), bookMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(imprMenu), mailMi);
  sep = gtk_separator_menu_item_new();  
  quitMi = gtk_menu_item_new_with_label("Quit");

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(fileMi), fileMenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), imprMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), sep);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), quitMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), fileMi);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(quitMi), "activate",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例在另一个菜单内创建一个菜单。 子菜单具有三个菜单项。 我们还添加了一个水平分隔符。

imprMenu = gtk_menu_new();
imprMi = gtk_menu_item_new_with_label("Import");
feedMi = gtk_menu_item_new_with_label("Import news feed...");
bookMi = gtk_menu_item_new_with_label("Import bookmarks...");
mailMi = gtk_menu_item_new_with_label("Import mail...");

这是带有菜单项的子菜单。

gtk_menu_item_set_submenu(GTK_MENU_ITEM(imprMi), imprMenu);

imprMenu子菜单被添加到其自己的菜单项中。

gtk_menu_shell_append(GTK_MENU_SHELL(imprMenu), feedMi);
gtk_menu_shell_append(GTK_MENU_SHELL(imprMenu), bookMi);
gtk_menu_shell_append(GTK_MENU_SHELL(imprMenu), mailMi);

使用gtk_menu_shell_append()函数将三个菜单项添加到子菜单。

sep = gtk_separator_menu_item_new();

使用gtk_separator_menu_item_new()函数创建水平菜单分隔符。

gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), imprMi);
gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), sep);

imprMi和分隔符通过gtk_menu_shell_append()函数添加到文件菜单。

Submenu

图:子菜单

图像菜单,助记符和加速器

GtkImageMenuItem是一个菜单项,在文本标签旁边带有一个图标。 由于用户可以禁用菜单图标的显示,因此我们仍然需要填写文本标签。加速器是用于激活菜单项的键盘快捷键。助记符是 GUI 元素的键盘快捷键。 它们以带下划线的字符表示。 请注意,在某些环境中,我们首先需要按无鼠标修饰符(通常为Alt)以显示带下划线的字符。

我们可能还配置了不显示菜单图像的环境。 要打开菜单图像,我们启动gconf-editor并转到/desktop/gnome/interface/menus_have_icons并检查选项。

imagemenu.c

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *fileMenu;
  GtkWidget *fileMi;
  GtkWidget *newMi;
  GtkWidget *openMi;
  GtkWidget *quitMi;

  GtkWidget *sep;

  GtkAccelGroup *accel_group = NULL;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Images");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();
  fileMenu = gtk_menu_new();

  accel_group = gtk_accel_group_new();
  gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);

  fileMi = gtk_menu_item_new_with_mnemonic("_File");
  newMi = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW, NULL);
  openMi = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);
  sep = gtk_separator_menu_item_new();
  quitMi = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group);

  gtk_widget_add_accelerator(quitMi, "activate", accel_group, 
      GDK_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); 

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(fileMi), fileMenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), newMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), openMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), sep);
  gtk_menu_shell_append(GTK_MENU_SHELL(fileMenu), quitMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), fileMi);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(quitMi), "activate",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例包含三个带有图标的菜单项。 菜单项可以用助记符选择。 退出菜单项具有键盘加速器。

accel_group = gtk_accel_group_new();
gtk_window_add_accel_group(GTK_WINDOW(window), accel_group);
...
quitMi = gtk_image_menu_item_new_from_stock(GTK_STOCK_QUIT, accel_group);

gtk_widget_add_accelerator(quitMi, "activate", accel_group, 
    GDK_q, GDK_CONTROL_MASK, GTK_ACCEL_VISIBLE); 

加速器组是一组键盘加速器,通常附加到顶层窗口。 在这里,我们创建 Ctrl + Q 键盘加速器。

fileMi = gtk_menu_item_new_with_mnemonic("_File");

gtk_menu_item_new_with_mnemonic()创建一个可以带有助记符的菜单项。 标签中的下划线表示菜单项的助记符。 该字符与无鼠标修饰符(通常为Alt)结合在一起。 在我们的情况下,我们创建了 Alt + F 助记符。

newMi = gtk_image_menu_item_new_from_stock(GTK_STOCK_NEW, NULL);
openMi = gtk_image_menu_item_new_from_stock(GTK_STOCK_OPEN, NULL);

gtk_image_menu_item_new_from_stock()创建一个GtkImageMenuItem,其中包含来自库存项目的图像和文本。

Menu items with icons

图:带图标的菜单项

CheckMenuItem

GtkCheckMenuItem是带有复选框的菜单项。

checkmenuitem.c

#include <gtk/gtk.h>

void toggle_statusbar(GtkWidget *widget, gpointer statusbar) {

  if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {

    gtk_widget_show(statusbar);
  } else {

    gtk_widget_hide(statusbar);
  }
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *menubar;
  GtkWidget *viewmenu;
  GtkWidget *view;
  GtkWidget *tog_stat;
  GtkWidget *statusbar;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "GtkCheckMenuItem");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  menubar = gtk_menu_bar_new();
  viewmenu = gtk_menu_new();

  view = gtk_menu_item_new_with_label("View");
  tog_stat = gtk_check_menu_item_new_with_label("View statusbar");
  gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(tog_stat), TRUE);

  gtk_menu_item_set_submenu(GTK_MENU_ITEM(view), viewmenu);
  gtk_menu_shell_append(GTK_MENU_SHELL(viewmenu), tog_stat);
  gtk_menu_shell_append(GTK_MENU_SHELL(menubar), view);
  gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);

  statusbar = gtk_statusbar_new();
  gtk_box_pack_end(GTK_BOX(vbox), statusbar, FALSE, TRUE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(tog_stat), "activate", 
        G_CALLBACK(toggle_statusbar), statusbar);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例在“查看”菜单中包含一个GtkCheckMenuItem。 如果该复选框已激活,则显示状态栏小部件。

tog_stat = gtk_check_menu_item_new_with_label("View statusbar");

gtk_check_menu_item_new_with_label()函数创建一个新的CheckMenuItem

statusbar = gtk_statusbar_new();

gtk_statusbar_new()函数创建一个新的GtkStatusbar小部件。 它用于向用户报告次要消息。

if (gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget))) {

  gtk_widget_show(statusbar);
} else {

  gtk_widget_hide(statusbar);
}

如果菜单项中的复选框被激活,我们将显示状态栏小部件。 否则状态栏将被隐藏。

GtkCheckMenuItem

图:GtkCheckMenuItem

弹出菜单

在下一个示例中,我们创建一个弹出菜单。 弹出菜单也称为上下文菜单。 当我们右键单击 GUI 对象时,通常会显示这种类型的菜单。

popupmenu.c

#include <gtk/gtk.h>

int show_popup(GtkWidget *widget, GdkEvent *event) {

  const gint RIGHT_CLICK = 3;

  if (event->type == GDK_BUTTON_PRESS) {

      GdkEventButton *bevent = (GdkEventButton *) event;

      if (bevent->button == RIGHT_CLICK) {      

          gtk_menu_popup(GTK_MENU(widget), NULL, NULL, NULL, NULL,
              bevent->button, bevent->time);
          }

      return TRUE;
  }

  return FALSE;
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *ebox;
  GtkWidget *pmenu;
  GtkWidget *hideMi;
  GtkWidget *quitMi;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Popup menu");

  ebox = gtk_event_box_new();
  gtk_container_add(GTK_CONTAINER(window), ebox);

  pmenu = gtk_menu_new();

  hideMi = gtk_menu_item_new_with_label("Minimize");
  gtk_widget_show(hideMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), hideMi);

  quitMi = gtk_menu_item_new_with_label("Quit");
  gtk_widget_show(quitMi);
  gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), quitMi);

  g_signal_connect_swapped(G_OBJECT(hideMi), "activate", 
      G_CALLBACK(gtk_window_iconify), GTK_WINDOW(window));    

  g_signal_connect(G_OBJECT(quitMi), "activate", 
      G_CALLBACK(gtk_main_quit), NULL);  

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect_swapped(G_OBJECT(ebox), "button-press-event", 
      G_CALLBACK(show_popup), pmenu);  

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在代码示例中,我们创建带有两个菜单项的弹出菜单。 第一个最小化窗口,第二个终止应用。

ebox = gtk_event_box_new();
gtk_container_add(GTK_CONTAINER(window), ebox);

为了处理按钮按下事件,我们创建了一个GtkEventBox

pmenu = gtk_menu_new();

弹出菜单是GtkMenu

hideMi = gtk_menu_item_new_with_label("Minimize");
gtk_widget_show(hideMi);
gtk_menu_shell_append(GTK_MENU_SHELL(pmenu), hideMi);

第一个菜单项已添加到弹出菜单。

g_signal_connect_swapped(G_OBJECT(hideMi), "activate", 
    G_CALLBACK(gtk_window_iconify), GTK_WINDOW(window)); 

选择第一个菜单项将使窗口最小化。 我们将“隐藏”菜单项的activate信号连接到gtk_window_iconify()函数。 术语“图标化”是“最小化”的同义词。

g_signal_connect_swapped(G_OBJECT(ebox), "button_press_event", 
    G_CALLBACK(show_popup), pmenu);   

当按下鼠标按钮时,会发出button-press-event信号。 我们将信号连接到show_popup()函数,然后将其传递给弹出菜单。

if (event->type == GDK_BUTTON_PRESS) {

在事件处理器内部,我们检查按钮按下事件的类型。

if (bevent->button == RIGHT_CLICK) {      

    gtk_menu_popup(GTK_MENU(widget), NULL, NULL, NULL, NULL,
        bevent->button, bevent->time);
}

当触发信号的按钮是鼠标右键时,我们将显示带有gtk_menu_popup()函数的弹出菜单。

Popup menu

图:弹出菜单

工具栏

菜单将我们可以在应用中使用的命令分组。 使用工具栏可以快速访问最常用的命令。 GtkToolbar是 GTK+ 中的工具栏小部件。 工具栏可以包含GtkToolItem子类的实例,例如 GtkToolButtonGtkSeparatorToolItem

toolbar.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *toolbar;
  GtkToolItem *newTb;
  GtkToolItem *openTb;
  GtkToolItem *saveTb;
  GtkToolItem *sep;
  GtkToolItem *exitTb;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "toolbar");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  newTb = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), newTb, -1);

  openTb = gtk_tool_button_new_from_stock(GTK_STOCK_OPEN);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), openTb, -1);

  saveTb = gtk_tool_button_new_from_stock(GTK_STOCK_SAVE);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), saveTb, -1);

  sep = gtk_separator_tool_item_new();
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep, -1); 

  exitTb = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), exitTb, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 5);

  g_signal_connect(G_OBJECT(exitTb), "clicked", 
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该代码示例创建了简单的工具栏示例。

toolbar = gtk_toolbar_new();

gtk_toolbar_new()函数创建一个新的GtkToolBar小部件。

gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS)

gtk_toolbar_set_style()函数将工具栏的视图更改为仅显示图标,仅显示文本或同时显示两者。 传递GTK_TOOLBAR_ICONS常量可使工具栏仅显示图标。

newTb = gtk_tool_button_new_from_stock(GTK_STOCK_NEW);

gtk_tool_button_new_from_stock()函数创建一个新的GtkToolButton,其中包含来自库存项目的图像和文本。

gtk_toolbar_insert(GTK_TOOLBAR(toolbar), new, -1);

gtk_toolbar_insert()函数将GtkToolItem插入工具栏的指定位置。 如果位置为负,则该项目将附加到工具栏的末尾。

sep = gtk_separator_tool_item_new();
gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep, -1); 

gtk_separator_tool_item_new()函数创建一个新的GtkSeparatorToolItem。 它通过gtk_toolbar_insert()函数插入到工具栏中。

Toolbar

图:工具栏

撤销重做

下面的示例演示如何禁用工具栏上的工具栏按钮。 这是 GUI 编程中的常见做法。 例如,保存按钮; 如果我们将文档的所有更改都保存到磁盘上,则在大多数文本编辑器中,“保存”按钮均处于禁用状态。 这样,应用会向用户指示所有更改都已保存。

undoredo.c

#include <gtk/gtk.h>

void undo_redo(GtkWidget *widget,  gpointer item) {

  static gint count = 2;
  const gchar *name = gtk_widget_get_name(widget);

  if (g_strcmp0(name, "undo") ) {
    count++;
  } else {
    count--;
  }

  if (count < 0) {
     gtk_widget_set_sensitive(widget, FALSE);
     gtk_widget_set_sensitive(item, TRUE);
  } 

  if (count > 5) {
     gtk_widget_set_sensitive(widget, FALSE);
     gtk_widget_set_sensitive(item, TRUE);
  }
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *toolbar;
  GtkToolItem *undo;
  GtkToolItem *redo;
  GtkToolItem *sep;
  GtkToolItem *exit;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Undo redo");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2);

  undo = gtk_tool_button_new_from_stock(GTK_STOCK_UNDO);
  gtk_widget_set_name(GTK_WIDGET(undo), "undo");
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), undo, -1);

  redo = gtk_tool_button_new_from_stock(GTK_STOCK_REDO);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), redo, -1);

  sep = gtk_separator_tool_item_new();
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), sep, -1); 

  exit = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), exit, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(undo), "clicked", 
        G_CALLBACK(undo_redo), redo);

  g_signal_connect(G_OBJECT(redo), "clicked", 
        G_CALLBACK(undo_redo), undo);

  g_signal_connect(G_OBJECT(exit), "clicked", 
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

我们的示例从 GTK+ 库存资源创建撤消和重做按钮。 单击几下后,每个按钮均被禁用。 按钮显示为灰色。

if (count < 0) {
   gtk_widget_set_sensitive(widget, FALSE);
   gtk_widget_set_sensitive(item, TRUE);
} 

if (count > 5) {
   gtk_widget_set_sensitive(widget, FALSE);
   gtk_widget_set_sensitive(item, TRUE);
}

gtk_widget_set_sensitive()函数用于激活或禁用工具栏按钮。

Undo redo

图:撤销和重做

在本章中,我们介绍了 GTK+ 中的菜单和工具栏。

GTK+ 布局管理

原文: http://zetcode.com/gui/gtk2/gtklayoutmanagement/

在本章中,我们将展示如何在窗口或对话框中布置窗口小部件。

在设计应用的 UI 时,我们决定要使用哪些小部件以及如何组织这些小部件。 为了组织窗口小部件,我们使用称为布局容器的专用非可见窗口小部件。 在本章中,我们将提及GtkAlignmentGtkFixedGtkVBoxGtkTable

GtkFixed

GtkFixed容器将子窗口小部件放置在固定位置并具有固定大小。 此容器不执行自动布局管理。 因此,它不适用于翻译,字体更改或主题。 在大多数应用中,我们不使用GtkFixed容器。 可能会有一些专门的区域可以使用容器(例如,定位图或图像)。

fixed.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *fixed;

  GtkWidget *btn1;
  GtkWidget *btn2;
  GtkWidget *btn3;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkFixed");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  fixed = gtk_fixed_new();
  gtk_container_add(GTK_CONTAINER(window), fixed);

  btn1 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), btn1, 150, 50);
  gtk_widget_set_size_request(btn1, 80, 30);

  btn2 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), btn2, 15, 15);
  gtk_widget_set_size_request(btn2, 80, 30);

  btn3 = gtk_button_new_with_label("Button");
  gtk_fixed_put(GTK_FIXED(fixed), btn3, 100, 100);
  gtk_widget_set_size_request(btn3, 80, 30);

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在我们的示例中,我们创建了三个按钮并将它们放置在固定坐标上。 当我们调整应用窗口的大小时,按钮将保持其大小和位置。

fixed = gtk_fixed_new();

get_fixed_new()函数创建一个GtkFixed容器。

gtk_fixed_put(GTK_FIXED(fixed), btn1, 150, 50);

使用gtk_fixed_put()函数将第一个按钮放置在坐标x = 150y = 50处。

gtk_widget_set_size_request(btn1, 80, 30);

gtk_widget_set_size_request()设置小部件的最小大小。 它是小部件可以接受的最小大小,同时仍可以正常运行并正确绘制自身。

GtkFixed container

图:GtkFixed容器

GtkAlignment

GtkAlignment控制小部件的对齐。 此外,它可以管理其缩放比例。

bottomleft.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *align;

  GtkWidget *lbl;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkAlignment");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  align = gtk_alignment_new(0, 1, 0, 0);
  lbl = gtk_label_new("bottom-left");

  gtk_container_add(GTK_CONTAINER(align), lbl);
  gtk_container_add(GTK_CONTAINER(window), align);

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,标签位于窗口的左下角。

align = gtk_alignment_new(0, 1, 0, 0);

gtk_alignment_new()函数创建GtkAlignment容器。 参数的取值范围是 0 到 1。第一个参数是水平对齐方式,其中 0 左,1 右。 第二个参数是垂直对齐方式,其中 0 是顶部,1 是底部。 第三个参数是水平比例尺,它是子窗口小部件水平扩展以填充未使用空间的数量。 值为 0 表示子窗口小部件永远不应扩展。 最后一个参数是垂直刻度。

lbl = gtk_label_new("bottom-left");

使用gtk_label_new()函数创建标签窗口小部件。

gtk_container_add(GTK_CONTAINER(align), lbl);

标签被添加到GtkAlignment容器中。

gtk_container_add(GTK_CONTAINER(window), align);

最后,将对齐容器放入窗口中。

GtkAlignment

图:GtkAlignment

GtkVBox

GtkVBox是一个垂直盒式容器。 它将其子窗口小部件放置在单个列中。 GtkHBox是一个非常相似的容器; 它将其子窗口小部件放在一行中。

vbox.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *settings;
  GtkWidget *accounts;
  GtkWidget *loans;
  GtkWidget *cash;
  GtkWidget *debts;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 250);
  gtk_window_set_title(GTK_WINDOW(window), "GtkVBox");
  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  vbox = gtk_vbox_new(TRUE, 1);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  settings = gtk_button_new_with_label("Settings");
  accounts = gtk_button_new_with_label("Accounts");
  loans = gtk_button_new_with_label("Loans");
  cash = gtk_button_new_with_label("Cash");
  debts = gtk_button_new_with_label("Debts");

  gtk_box_pack_start(GTK_BOX(vbox), settings, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), accounts, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), loans, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), cash, TRUE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), debts, TRUE, TRUE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

本示例将五个按钮打包到一栏中。 如果我们调整应用窗口的大小,则子窗口小部件也将被调整大小。

vbox = gtk_vbox_new(TRUE, 1);

gtk_vbox_new()函数创建一个GtkVBox容器。 我们将homogeneous参数设置为TRUE。 这意味着我们所有的按钮都将具有相同的大小。 小部件之间的间距设置为 1 像素。

gtk_box_pack_start(GTK_BOX(vbox), settings, TRUE, TRUE, 0);

gtk_box_pack_start()函数将小部件添加到框中。 前两个参数是盒子容器和子窗口小部件。 接下来的三个参数是expandfillpadding。 注意,如果expand参数设置为FALSE,则fill参数无效。 同样,如果我们创建的容器的均质参数设置为TRUE,则expand参数无效。 在我们的情况下,当窗口放大且窗口小部件填充额外区域时,“设置”按钮将获得额外的空间。

GtkVBox container

图:GtkVBox容器

GtkTable

GtkTable小部件按行和列排列小部件。

table.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *table;
  GtkWidget *button;

  gchar *values[16] = { "7", "8", "9", "/", 
     "4", "5", "6", "*",
     "1", "2", "3", "-",
     "0", ".", "=", "+"
  };

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 180);
  gtk_window_set_title(GTK_WINDOW(window), "GtkTable");

  gtk_container_set_border_width(GTK_CONTAINER(window), 5);

  table = gtk_table_new(4, 4, TRUE);
  gtk_table_set_row_spacings(GTK_TABLE(table), 2);
  gtk_table_set_col_spacings(GTK_TABLE(table), 2);

  int i = 0;
  int j = 0;
  int pos = 0;

  for (i=0; i < 4; i++) {
    for (j=0; j < 4; j++) {
      button = gtk_button_new_with_label(values[pos]);
      gtk_table_attach_defaults(GTK_TABLE(table), button, j, j+1, i, i+1);
      pos++;
    }
  }

  gtk_container_add(GTK_CONTAINER(window), table);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在此示例中,我们创建了一组在计算器中看到的按钮。

table = gtk_table_new(4, 4, TRUE);

我们创建一个具有 4 行 4 列的新GtkTable小部件。 当我们将TRUE传递给第三个参数时,所有表单元格的大小都将调整为包含最大窗口小部件的单元格的大小。

gtk_table_set_row_spacings(GTK_TABLE(table), 2);
gtk_table_set_col_spacings(GTK_TABLE(table), 2);

我们在行和列之间设置一些空间。

for (i=0; i < 4; i++) {
  for (j=0; j < 4; j++) {
    button = gtk_button_new_with_label(values[pos]);
    gtk_table_attach_defaults(GTK_TABLE(table), button, j, j+1, i, i+1 );
    pos++;
  }
}

这段代码创建了 16 个按钮,并将它们放入容器中。 gtk_table_attach_defaults()将子项添加到具有相同填充和扩展选项的表容器中。

GtkTable

图:GtkTable

角按钮

下一个示例在窗口的右下角放置两个按钮。

cornerbuttons.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *okBtn;
  GtkWidget *clsBtn;

  GtkWidget *vbox;
  GtkWidget *hbox;
  GtkWidget *halign;
  GtkWidget *valign;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 350, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Corner buttons");
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);

  vbox = gtk_vbox_new(FALSE, 5);

  valign = gtk_alignment_new(0, 1, 0, 0);
  gtk_container_add(GTK_CONTAINER(vbox), valign);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  hbox = gtk_hbox_new(TRUE, 3);

  okBtn = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(okBtn, 70, 30);
  gtk_container_add(GTK_CONTAINER(hbox), okBtn);
  clsBtn = gtk_button_new_with_label("Close");
  gtk_container_add(GTK_CONTAINER(hbox), clsBtn);

  halign = gtk_alignment_new(1, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), hbox);

  gtk_box_pack_start(GTK_BOX(vbox), halign, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,我们使用一个水平框,一个垂直框和两个对齐容器。

valign = gtk_alignment_new(0, 1, 0, 0);

此对齐容器将其子窗口小部件放在底部。

gtk_container_add(GTK_CONTAINER(vbox), valign);

在这里,我们将对齐小部件放置在垂直框中。

hbox = gtk_hbox_new(TRUE, 3);

okBtn = gtk_button_new_with_label("OK");
gtk_widget_set_size_request(okBtn, 70, 30);
gtk_container_add(GTK_CONTAINER(hbox), okBtn);
clsBtn = gtk_button_new_with_label("Close");
gtk_container_add(GTK_CONTAINER(hbox), clsBtn);

我们创建一个水平框,并在其中放置两个按钮。 gtk_widget_set_size_request()设置小部件的最小大小。 由于我们已经将GtkHBoxhomogeneous参数设置为TRUE,因此另一个按钮也被调整为新的大小。

halign = gtk_alignment_new(1, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(halign), hbox);

gtk_box_pack_start(GTK_BOX(vbox), halign, FALSE, FALSE, 0);

这将创建一个对齐容器,将其子窗口小部件放在右侧。 我们将水平框添加到对齐容器中,然后将对齐容器包装到垂直框中。 对齐容器只能使用一个子窗口小部件; 因此,我们还必须使用盒子。

Corner buttons

图:角按钮

窗口

接下来,我们将创建一个更高级的示例。 我们显示一个可以在 JDeveloper 中找到的窗口。

Windows dialog in JDeveloper

图:窗口 dialog in JDeveloper

该对话框显示所有打开的窗口,或更确切地说是 JDeveloper 应用中的选项卡。

windows.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *table;
  GtkWidget *title;
  GtkWidget *wins;

  GtkWidget *halign;
  GtkWidget *halign2;
  GtkWidget *valign;

  GtkWidget *actBtn;
  GtkWidget *clsBtn;
  GtkWidget *hlpBtn;
  GtkWidget *okBtn;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_widget_set_size_request (window, 300, 250);

  gtk_window_set_title(GTK_WINDOW(window), "Windows");

  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  table = gtk_table_new(6, 4, FALSE);
  gtk_table_set_col_spacings(GTK_TABLE(table), 3);
  gtk_table_set_row_spacing(GTK_TABLE(table), 0, 3);

  title = gtk_label_new("Windows");
  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), title);
  gtk_table_attach(GTK_TABLE(table), halign, 0, 1, 0, 1, 
      GTK_FILL, GTK_FILL, 0, 0);

  wins = gtk_text_view_new();
  gtk_text_view_set_editable(GTK_TEXT_VIEW(wins), FALSE);
  gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(wins), FALSE);
  gtk_table_attach(GTK_TABLE(table), wins, 0, 2, 1, 3, 
      GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 1, 1);

  actBtn = gtk_button_new_with_label("Activate");
  gtk_widget_set_size_request(actBtn, 50, 30);
  gtk_table_attach(GTK_TABLE(table), actBtn, 3, 4, 1, 2, 
      GTK_FILL, GTK_SHRINK, 1, 1);

  valign = gtk_alignment_new(0, 0, 0, 0);
  clsBtn = gtk_button_new_with_label("Close");

  gtk_widget_set_size_request(clsBtn, 70, 30);
  gtk_container_add(GTK_CONTAINER(valign), clsBtn);
  gtk_table_set_row_spacing(GTK_TABLE(table), 1, 3);
  gtk_table_attach(GTK_TABLE(table), valign, 3, 4, 2, 3, 
      GTK_FILL, GTK_FILL | GTK_EXPAND, 1, 1);

  halign2 = gtk_alignment_new(0, 1, 0, 0);
  hlpBtn = gtk_button_new_with_label("Help");
  gtk_container_add(GTK_CONTAINER(halign2), hlpBtn);
  gtk_widget_set_size_request(hlpBtn, 70, 30);
  gtk_table_set_row_spacing(GTK_TABLE(table), 3, 5);
  gtk_table_attach(GTK_TABLE(table), halign2, 0, 1, 4, 5, 
      GTK_FILL, GTK_FILL, 0, 0);

  okBtn = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(okBtn, 70, 30);
  gtk_table_attach(GTK_TABLE(table), okBtn, 3, 4, 4, 5, 
      GTK_FILL, GTK_FILL, 0, 0);

  gtk_container_add(GTK_CONTAINER(window), table);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);
  gtk_main();

  return 0;
}

该示例使用一个表容器和三个对齐容器。

table = gtk_table_new(6, 4, FALSE);

创建一个GtkTable容器。 它有六行四列。

gtk_table_set_col_spacings(GTK_TABLE(table), 3);

gtk_table_set_col_spacings()将表中每列之间的间隔设置为 3。

gtk_table_set_row_spacing(GTK_TABLE(table), 0, 3);

gtk_table_row_spacing()设置第一行和第二行之间的空间。

title = gtk_label_new("Windows");
halign = gtk_alignment_new(0, 0, 0, 0);
gtk_container_add(GTK_CONTAINER(halign), title);
gtk_table_attach(GTK_TABLE(table), halign, 0, 1, 0, 1, 
    GTK_FILL, GTK_FILL, 0, 0);

这段代码创建了一个左对齐的标签。 标签放置在GtkTable容器的第一行和第一列中。

wins = gtk_text_view_new();
gtk_text_view_set_editable(GTK_TEXT_VIEW(wins), FALSE);
gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(wins), FALSE);
gtk_table_attach(GTK_TABLE(table), wins, 0, 2, 1, 3, 
    GTK_FILL | GTK_EXPAND, GTK_FILL | GTK_EXPAND, 1, 1);

GtkText小部件跨越两行两列。 我们使用gtk_text_view_set_editable()方法使窗口小部件不可编辑,并使用gtk_text_view_set_cursor_visible()方法隐藏其光标。

valign = gtk_alignment_new(0, 0, 0, 0);
clsBtn = gtk_button_new_with_label("Close");

gtk_widget_set_size_request(clsBtn, 70, 30);
gtk_container_add(GTK_CONTAINER(valign), clsBtn);
gtk_table_set_row_spacing(GTK_TABLE(table), 1, 3);
gtk_table_attach(GTK_TABLE(table), valign, 3, 4, 2, 3, 
    GTK_FILL, GTK_FILL | GTK_EXPAND, 1, 1);

我们将“关闭”按钮放在文本视图窗口小部件旁边的第四列中。 我们将按钮添加到对齐小部件中,以便我们可以将其对齐到顶部。

halign2 = gtk_alignment_new(0, 1, 0, 0);
hlpBtn = gtk_button_new_with_label("Help");
gtk_container_add(GTK_CONTAINER(halign2), hlpBtn);
gtk_widget_set_size_request(hlpBtn, 70, 30);
gtk_table_set_row_spacing(GTK_TABLE(table), 3, 5);
gtk_table_attach(GTK_TABLE(table), halign2, 0, 1, 4, 5, 
    GTK_FILL, GTK_FILL, 0, 0);

“帮助”按钮向左对齐。 它位于文本小部件下方。 我们在文本小部件和按钮之间放置一些空间。

okBtn = gtk_button_new_with_label("OK");
gtk_widget_set_size_request(okBtn, 70, 30);
gtk_table_attach(GTK_TABLE(table), okBtn, 3, 4, 4, 5, 
    GTK_FILL, GTK_FILL, 0, 0);

“确定”按钮转到“激活”和“关闭”按钮下面的第二列。

Windows

图:窗口

本章专门介绍布局管理。

GTK+ 事件和信号

原文: http://zetcode.com/gui/gtk2/gtkevents/

在 GTK+ 编程教程的这一部分中,我们讨论事件系统。

GTK+ 是事件驱动的系统。 所有 GUI 应用都是事件驱动的。 应用启动一个主循环,该循环不断检查新生成的事件。 如果没有事件,则应用将等待并且不执行任何操作。 在 GTK+ 中,事件是来自 X 服务器的消息。 当事件到达窗口小部件时,它可以通过发出信号对此事件做出反应。 GTK+ 程序员可以将特定的回调连接到信号。 回调是对信号做出反应的处理函数。

点击按钮

触发按钮时,它会发送clicked信号。 可以通过鼠标指针或使用空格键触发按钮(只要按钮具有焦点)。

buttonclick.c

#include <gtk/gtk.h>

void button_clicked(GtkWidget *widget, gpointer data) {

  g_print("clicked\n");
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *halign;
  GtkWidget *btn;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkButton");
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  halign = gtk_alignment_new(0, 0, 0, 0);
  btn = gtk_button_new_with_label("Click");
  gtk_widget_set_size_request(btn, 70, 30);

  gtk_container_add(GTK_CONTAINER(halign), btn);
  gtk_container_add(GTK_CONTAINER(window), halign);

  g_signal_connect(G_OBJECT(btn), "clicked", 
      G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在应用中,我们有两个信号:clicked信号和destroy信号。

g_signal_connect(G_OBJECT(btn), "clicked", 
    G_CALLBACK(button_clicked), NULL);

我们使用g_signal_connect()函数将clicked信号连接到button_clicked()回调。

void button_clicked(GtkWidget *widget, gpointer data) {

  g_print("clicked\n");
}

回调将"clicked"字符串打印到控制台。 回调函数的第一个参数是发出信号的对象。 在我们的例子中是单击按钮。 第二个参数是可选的。 我们可能会向回调发送一些数据。 在我们的案例中,我们没有发送任何数据; 我们为g_signal_connect()函数的第四个参数提供了NULL值。

g_signal_connect(G_OBJECT(window), "destroy", 
     G_CALLBACK(gtk_main_quit), NULL);

如果按标题栏右上角的 x 按钮,或按 Atl + F4 ,则会发出destroy信号。 调用gtk_main_quit()函数,该函数将终止应用。

移动窗口

下一个示例显示了我们如何应对窗口移动事件。

moveevent.c

#include <gtk/gtk.h>

void configure_callback(GtkWindow *window, 
      GdkEvent *event, gpointer data) {

   int x, y;
   GString *buf;

   x = event->configure.x;
   y = event->configure.y;

   buf = g_string_new(NULL);   
   g_string_printf(buf, "%d, %d", x, y);

   gtk_window_set_title(window, buf->str);

   g_string_free(buf, TRUE);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  g_signal_connect(G_OBJECT(window), "configure-event",
        G_CALLBACK(configure_callback), NULL);

  gtk_widget_show(window);
  gtk_main();

  return 0;
}

在示例中,我们在标题栏中显示了窗口左上角的当前位置。

gtk_widget_add_events(GTK_WIDGET(window), GDK_CONFIGURE);

小部件的事件掩码确定特定小部件将接收的事件类型。 一些事件是预先配置的,其他事件必须添加到事件掩码中。 gtk_widget_add_events()GDK_CONFIGURE事件类型添加到掩码。 GDK_CONFIGURE事件类型说明了窗口的所有大小,位置和堆叠顺序。

g_signal_connect(G_OBJECT(window), "configure-event",
    G_CALLBACK(configure_callback), NULL);

当窗口小部件的窗口的大小,位置或栈发生更改时,将发出configure-event

void configure_callback(GtkWindow *window, 
      GdkEvent *event, gpointer data) {

   int x, y;
   GString *buf;

   x = event->configure.x;
   y = event->configure.y;

   buf = g_string_new(NULL);   
   g_string_printf(buf, "%d, %d", x, y);

   gtk_window_set_title(window, buf->str);

   g_string_free(buf, TRUE);
}

回调函数具有三个参数:发出信号的对象,GdkEvent和可选数据。 我们确定 x,y 坐标,构建一个字符串,并将其设置为窗口标题。

Move event

图:移动事件

输入信号

以下示例显示了我们如何对enter信号做出反应。 当我们使用鼠标指针进入小部件的区域时,将发出enter信号。

entersignal.c

#include <gtk/gtk.h>

void enter_button(GtkWidget *widget, gpointer data) {

  GdkColor col = {0, 27000, 30000, 35000};   

  gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &col);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *halign;
  GtkWidget *btn;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_window_set_title(GTK_WINDOW(window), "Enter signal");

  halign = gtk_alignment_new(0, 0, 0, 0);

  btn = gtk_button_new_with_label("Button");
  gtk_widget_set_size_request(btn, 70, 30);

  gtk_container_add(GTK_CONTAINER(halign), btn);
  gtk_container_add(GTK_CONTAINER(window), halign);

  g_signal_connect(G_OBJECT(btn), "enter", 
      G_CALLBACK(enter_button), NULL);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在该示例中,当我们将鼠标指针悬停在其上方时,按钮小部件的背景颜色会更改。

g_signal_connect(G_OBJECT(btn), "enter", 
    G_CALLBACK(enter_button), NULL);

enter信号出现时,我们调用enter_button()用户功能。

void enter_button(GtkWidget *widget, gpointer data) {

  GdkColor col = {0, 27000, 30000, 35000};   

  gtk_widget_modify_bg(widget, GTK_STATE_PRELIGHT, &col);
}

在回调内部,我们通过调用gtk_widget_modify_bg()函数来更改按钮的背景。

断开回调

我们可以从信号断开回调。 下一个代码示例演示了这种情况。

disconnect.c

#include <gtk/gtk.h>

gint handler_id;

void button_clicked(GtkWidget *widget, gpointer data) {

  g_print("clicked\n");
}

void toogle_signal(GtkWidget *widget, gpointer window) {

  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
     handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
           G_CALLBACK(button_clicked), NULL);
  } else {
     g_signal_handler_disconnect(window, handler_id);
  }
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *btn;
  GtkWidget *cb;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_window_set_title(GTK_WINDOW(window), "Disconnect");

  hbox = gtk_hbox_new(FALSE, 15);

  btn = gtk_button_new_with_label("Click");
  gtk_widget_set_size_request(btn, 70, 30);
  gtk_box_pack_start(GTK_BOX(hbox), btn, FALSE, FALSE, 0);

  cb = gtk_check_button_new_with_label("Connect");
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), TRUE);
  gtk_box_pack_start(GTK_BOX(hbox), cb, FALSE, FALSE, 0);

  vbox = gtk_vbox_new(FALSE, 5);
  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);  

  handler_id = g_signal_connect(G_OBJECT(btn), "clicked", 
      G_CALLBACK(button_clicked), NULL);

  g_signal_connect(G_OBJECT(cb), "clicked",
      G_CALLBACK(toogle_signal), (gpointer) btn);

  g_signal_connect(G_OBJECT(window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在代码示例中,我们有一个按钮和一个复选框。 复选框用于从按钮的clicked信号连接或断开回调。

handler_id = g_signal_connect(G_OBJECT(btn), "clicked", 
    G_CALLBACK(button_clicked), NULL);

g_signal_connect()返回唯一标识回调的处理器 ID。

if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
   handler_id = g_signal_connect(G_OBJECT(window), "clicked", 
         G_CALLBACK(button_clicked), NULL);
} else {
   g_signal_handler_disconnect(window, handler_id);
}

此代码确定复选框的状态。 根据状态,它通过g_signal_connect()函数连接回调或通过g_signal_handler_disconnect()函数断开连接。

Disconnect

图:断开连接

拖放示例

在下一个示例中,我们显示无边界窗口,并学习如何拖动和移动这样的窗口。

dragdrop.c

#include <gtk/gtk.h>

gboolean on_button_press(GtkWidget* widget,
  GdkEventButton *event, GdkWindowEdge edge) {

  if (event->type == GDK_BUTTON_PRESS) {

    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
          event->x_root,
          event->y_root,
          event->time);
    }
  }

  return TRUE;
}

int main(int argc, char *argv[]) {

  GtkWidget *window;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Drag & drop");
  gtk_window_set_decorated(GTK_WINDOW(window), FALSE);
  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(G_OBJECT(window), "button-press-event",
      G_CALLBACK(on_button_press), NULL);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show(window);

  gtk_main();

  return 0;
}

该示例演示了无边界窗口的拖放操作。

gtk_window_set_decorated(GTK_WINDOW(window), FALSE);

我们使用gtk_window_set_decorated()函数删除窗口装饰。 这意味着该窗口将没有边框和标题栏。

g_signal_connect(G_OBJECT(window), "button-press-event",
    G_CALLBACK(on_button_press), NULL);

我们将窗口连接到button-press-event信号。

gboolean on_button_press(GtkWidget* widget,
  GdkEventButton *event, GdkWindowEdge edge) {

  if (event->type == GDK_BUTTON_PRESS) {

    if (event->button == 1) {
      gtk_window_begin_move_drag(GTK_WINDOW(gtk_widget_get_toplevel(widget)),
          event->button,
          event->x_root,
          event->y_root,
          event->time);
    }
  }

  return TRUE;
}

on_button_press()函数内,我们执行拖放操作。 我们检查是否按下了鼠标左键。 然后我们调用gtk_window_begin_move_drag()函数,该函数开始移动窗口。

计时器示例

下面的示例演示一个计时器示例。 当我们有一些重复的任务时使用计时器。 可能是时钟,倒数,视觉效果或动画。

timer.c

#include <cairo.h>
#include <gtk/gtk.h>

gchar buf[256];

gboolean on_expose_event(GtkWidget *widget,
    GdkEventExpose *event,
    gpointer data) {

  cairo_t *cr;

  cr = gdk_cairo_create(widget->window);

  cairo_move_to(cr, 30, 30);
  cairo_set_font_size(cr, 15);
  cairo_show_text(cr, buf);

  cairo_destroy(cr);

  return FALSE;
}

gboolean time_handler(GtkWidget *widget) {

  if (widget->window == NULL) return FALSE;

  GDateTime *now = g_date_time_new_now_local(); 
  gchar *my_time = g_date_time_format(now, "%H:%M:%S");

  g_sprintf(buf, "%s", my_time);

  g_free(my_time);
  g_date_time_unref(now);

  gtk_widget_queue_draw(widget);

  return TRUE;
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *darea;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  darea = gtk_drawing_area_new();
  gtk_container_add(GTK_CONTAINER(window), darea);

  g_signal_connect(darea, "expose-event",
      G_CALLBACK(on_expose_event), NULL);
  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);

  gtk_window_set_title(GTK_WINDOW(window), "Timer");
  g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);
  gtk_widget_show_all(window);
  time_handler(window);

  gtk_main();

  return 0;
}

该示例在窗口上显示当前本地时间。 也使用 Cairo 2D 库。

g_signal_connect(darea, "expose-event",
    G_CALLBACK(on_expose_event), NULL);

我们在on_expose_event()回调中绘制时间。 回调连接到expose-event信号,该信号在要重绘窗口时发出。

g_timeout_add(1000, (GSourceFunc) time_handler, (gpointer) window);

此功能注册计时器。 time_handler()函数会定期重复调用。 就我们而言,每一秒钟。 将调用计时器函数,直到返回FALSE

time_handler(window);

这将立即调用计时器函数。 否则,将延迟一秒钟。

cairo_t *cr;

cr = gdk_cairo_create(widget->window);

cairo_move_to(cr, 30, 30);
cairo_set_font_size(cr, 15);
cairo_show_text(cr, buf);

cairo_destroy(cr);

此代码在窗口上绘制当前时间。 有关 Cairo 2D 库的更多信息,请参见 ZetCode 的 Cairo 图形教程。

if (widget->window == NULL) return FALSE;

当窗口被破坏时,可能会调用计时器函数。 这行代码可以防止处理已经销毁的窗口小部件。

GDateTime *now = g_date_time_new_now_local(); 
gchar *my_time = g_date_time_format(now, "%H:%M:%S");

g_sprintf(buf, "%s", my_time);

这些行确定当前的本地时间。 时间存储在全局buf变量中。

gtk_widget_queue_draw(widget);

gtk_widget_queue_draw()函数使窗口区域无效,然后发出expose-event信号。

本章是关于 GTK+ 中的事件的。

GTK+ 对话框

原文: http://zetcode.com/gui/gtk2/gtkdialogs/

在 GTK+ 编程教程的这一部分中,我们使用对话框。

对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。

MessageDialog

消息对话框是方便的对话框,可向应用的用户提供消息。 该消息包含文本和图像数据。

messagedialogs.c

#include <gtk/gtk.h>

void show_info(GtkWidget *widget, gpointer window) {

  GtkWidget *dialog;
  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_INFO,
            GTK_BUTTONS_OK,
            "Download Completed");
  gtk_window_set_title(GTK_WINDOW(dialog), "Information");
  gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_destroy(dialog);
}

void show_error(GtkWidget *widget, gpointer window) {

  GtkWidget *dialog;
  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_ERROR,
            GTK_BUTTONS_OK,
            "Error loading file");
  gtk_window_set_title(GTK_WINDOW(dialog), "Error");
  gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_destroy(dialog);
}

void show_question(GtkWidget *widget, gpointer window) {

  GtkWidget *dialog;
  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_QUESTION,
            GTK_BUTTONS_YES_NO,
            "Are you sure to quit?");
  gtk_window_set_title(GTK_WINDOW(dialog), "Question");
  gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_destroy(dialog);
}

void show_warning(GtkWidget *widget, gpointer window) {

  GtkWidget *dialog;
  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_WARNING,
            GTK_BUTTONS_OK,
            "Unallowed operation");
  gtk_window_set_title(GTK_WINDOW(dialog), "Warning");
  gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_destroy(dialog);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *table;

  GtkWidget *info;
  GtkWidget *warn;
  GtkWidget *que;
  GtkWidget *err;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 220, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Message dialogs");

  table = gtk_table_new(2, 2, TRUE);
  gtk_table_set_row_spacings(GTK_TABLE(table), 2);
  gtk_table_set_col_spacings(GTK_TABLE(table), 2);

  info = gtk_button_new_with_label("Info");
  warn = gtk_button_new_with_label("Warning");
  que = gtk_button_new_with_label("Question");
  err = gtk_button_new_with_label("Error");

  gtk_table_attach(GTK_TABLE(table), info, 0, 1, 0, 1, 
      GTK_FILL, GTK_FILL, 3, 3);
  gtk_table_attach(GTK_TABLE(table), warn, 1, 2, 0, 1, 
      GTK_FILL, GTK_FILL, 3, 3);
  gtk_table_attach(GTK_TABLE(table), que, 0, 1, 1, 2, 
      GTK_FILL, GTK_FILL, 3, 3);
  gtk_table_attach(GTK_TABLE(table), err, 1, 2, 1, 2, 
      GTK_FILL, GTK_FILL, 3, 3);

  gtk_container_add(GTK_CONTAINER(window), table);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  g_signal_connect(G_OBJECT(info), "clicked", 
        G_CALLBACK(show_info), (gpointer) window); 

  g_signal_connect(G_OBJECT(warn), "clicked", 
        G_CALLBACK(show_warning), (gpointer) window); 

  g_signal_connect(G_OBJECT(que), "clicked", 
        G_CALLBACK(show_question), (gpointer) window); 

  g_signal_connect(G_OBJECT(err), "clicked", 
        G_CALLBACK(show_error), (gpointer) window); 

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在我们的示例中,我们显示了四种消息对话框:信息,警告,问题和错误消息对话框。

void show_question(GtkWidget *widget, gpointer window) {

  GtkWidget *dialog;
  dialog = gtk_message_dialog_new(GTK_WINDOW(window),
            GTK_DIALOG_DESTROY_WITH_PARENT,
            GTK_MESSAGE_QUESTION,
            GTK_BUTTONS_YES_NO,
            "Are you sure to quit?");
  gtk_window_set_title(GTK_WINDOW(dialog), "Question");
  gtk_dialog_run(GTK_DIALOG(dialog));
  gtk_widget_destroy(dialog);
}

show_question()函数中,我们弹出消息对话框。 消息对话框是使用gtk_message_dialog_new()调用创建的。 函数的参数指定我们创建哪种消息对话框。 GTK_MESSAGE_QUESTION常量创建一个问题类型对话框。 GTK_BUTTONS_YES_NO常量将在对话框中添加“是”和“否”按钮。 最后一个参数是我们在对话框中显示的文本。 gtk_dialog_run()函数显示对话框并阻塞主循环,直到对话框响应或被破坏为止。 该对话框必须使用gtk_widget_destroy()函数销毁。

Information message dialog

Warning message dialog

Question message dialog

Error message dialog

图:消息对话框

GtkAboutDialog

GtkAboutDialog是一个对话框,其目的是显示有关应用的信息。 它可以显示徽标,应用名称,版本,版权,网站和许可证信息。 也有可能对作者,文档撰写者,翻译者和艺术家予以赞扬。

aboutdialog.c

#include <gtk/gtk.h>

void show_about(GtkWidget *widget, gpointer data) {

  GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file("battery.png", NULL);

  GtkWidget *dialog = gtk_about_dialog_new();
  gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(dialog), "Battery");
  gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "0.9"); 
  gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog),"(c) Jan Bodnar");
  gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog), 
     "Battery is a simple tool for battery checking.");
  gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), 
     "http://www.batteryhq.net");
  gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), pixbuf);
  g_object_unref(pixbuf), pixbuf = NULL;
  gtk_dialog_run(GTK_DIALOG (dialog));
  gtk_widget_destroy(dialog);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *about;
  GdkPixbuf *battery;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 220, 150);
  gtk_window_set_title(GTK_WINDOW(window), "Battery");

  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_widget_add_events(window, GDK_BUTTON_PRESS_MASK);

  g_signal_connect(G_OBJECT(window), "button-press-event", 
        G_CALLBACK(show_about), (gpointer) window); 

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该代码示例使用具有某些功能的GtkAboutDialog。 单击窗口的客户区域会弹出对话框。

GtkWidget *dialog = gtk_about_dialog_new();

GtkAboutDialog是使用gtk_about_dialog_new()函数创建的。

gtk_about_dialog_set_name(GTK_ABOUT_DIALOG(dialog), "Battery");
gtk_about_dialog_set_version(GTK_ABOUT_DIALOG(dialog), "0.9"); 
gtk_about_dialog_set_copyright(GTK_ABOUT_DIALOG(dialog), 
    "(c) Jan Bodnar");

这些函数调用设置应用的名称,版本和版权。

gtk_about_dialog_set_comments(GTK_ABOUT_DIALOG(dialog), 
    "Battery is a simple tool for battery checking.");
gtk_about_dialog_set_website(GTK_ABOUT_DIALOG(dialog), 
    "http://www.batteryhq.net");

这些行设置了描述性注释和应用的网站。

GdkPixbuf *pixbuf = gdk_pixbuf_new_from_file("battery.png", NULL);
...
gtk_about_dialog_set_logo(GTK_ABOUT_DIALOG(dialog), pixbuf);
g_object_unref(pixbuf), pixbuf = NULL;

此代码创建应用的徽标。

GtkAboutDialog

图:GtkAboutDialog

GtkFontSelectionDialog

GtkFontSelectionDialog是用于选择字体的对话框。 它通常用于进行一些文本编辑或格式化的应用中。

fontdialog.c

#include <gtk/gtk.h>

void select_font(GtkWidget *widget, gpointer label) {

  GtkResponseType result;

  GtkWidget *dialog = gtk_font_selection_dialog_new("Select Font");

  result = gtk_dialog_run(GTK_DIALOG(dialog));

  if (result == GTK_RESPONSE_OK || result == GTK_RESPONSE_APPLY) {

     PangoFontDescription *font_desc;
     gchar *fontname = gtk_font_selection_dialog_get_font_name(
                            GTK_FONT_SELECTION_DIALOG(dialog));

     font_desc = pango_font_description_from_string(fontname);

     gtk_widget_modify_font(GTK_WIDGET(label), font_desc);

     g_free(fontname);
   }

  gtk_widget_destroy(dialog);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *label;
  GtkWidget *vbox;

  GtkWidget *toolbar;
  GtkToolItem *font;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 280, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Font Selection Dialog");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2);

  font = gtk_tool_button_new_from_stock(GTK_STOCK_SELECT_FONT);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), font, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 5);

  label = gtk_label_new("ZetCode");
  gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
  gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 5);

  g_signal_connect(G_OBJECT(font), "clicked", 
        G_CALLBACK(select_font), label);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在代码示例中,我们在窗口中心放置了一个简单标签。 我们通过单击工具栏按钮显示一个字体选择对话框。

GtkWidget *dialog = gtk_font_selection_dialog_new("Select Font");
result = gtk_dialog_run(GTK_DIALOG(dialog));

我们使用gtk_font_selection_dialog_new()函数创建GtkFontSelectionDialog,然后使用gtk_dialog_run()函数运行它。

if (result == GTK_RESPONSE_OK || result == GTK_RESPONSE_APPLY) {

    PangoFontDescription *font_desc;
    gchar *fontname = gtk_font_selection_dialog_get_font_name(
                        GTK_FONT_SELECTION_DIALOG(dialog));

    font_desc = pango_font_description_from_string(fontname);

    gtk_widget_modify_font(GTK_WIDGET(label), font_desc);

    g_free(fontname);
}

如果用户单击“确定”或“应用”按钮,则继续。 我们使用gtk_font_selection_dialog_get_font_name()函数获得选定的字体名称。 然后,将标签的字体更改为选定的字体名称。

GtkFontSelectionDialog

图:GtkFontSelectionDialog

GtkColorSelectionDialog

GtkColorSelectionDialog是用于选择颜色的对话框。

colordialog.c

#include <gtk/gtk.h>

void select_font(GtkWidget *widget, gpointer label) {

  GtkResponseType result;
  GtkColorSelection *colorsel;

  GtkWidget *dialog = gtk_color_selection_dialog_new("Font Color");

  result = gtk_dialog_run(GTK_DIALOG(dialog));

  if (result == GTK_RESPONSE_OK) {

    GdkColor color;

    colorsel = GTK_COLOR_SELECTION(
                   GTK_COLOR_SELECTION_DIALOG(dialog)->colorsel);
    gtk_color_selection_get_current_color(colorsel,
                                &color);

    gtk_widget_modify_fg(GTK_WIDGET(label),
                             GTK_STATE_NORMAL,
                             &color);
  } 

  gtk_widget_destroy(dialog);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *widget;
  GtkWidget *label;
  GtkWidget *vbox;

  GtkWidget *toolbar;
  GtkToolItem *font;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 280, 200);
  gtk_window_set_title(GTK_WINDOW(window), "Color Selection Dialog");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  gtk_container_set_border_width(GTK_CONTAINER(toolbar), 2);

  font = gtk_tool_button_new_from_stock(GTK_STOCK_SELECT_COLOR);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), font, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 5);

  label = gtk_label_new("ZetCode");
  gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
  gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 5);

  g_signal_connect(G_OBJECT(font), "clicked", 
        G_CALLBACK(select_font), label);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例与上一个示例非常相似。 这次我们更改标签的颜色。

GtkWidget *dialog = gtk_color_selection_dialog_new("Font Color");
result = gtk_dialog_run(GTK_DIALOG(dialog));

我们创建并显示GtkColorSelectionDialog

if (result == GTK_RESPONSE_OK) {

  GdkColor color;

  colorsel = GTK_COLOR_SELECTION(
                 GTK_COLOR_SELECTION_DIALOG(dialog)->colorsel);
  gtk_color_selection_get_current_color(colorsel,
                 &color);

  gtk_widget_modify_fg(GTK_WIDGET(label),
                 GTK_STATE_NORMAL,
                 &color);
} 

如果按“确定”按钮,我们将获得颜色并修改标签的颜色。 颜色值通过gtk_color_selection_get_current_color()函数返回。

GtkColorSelectionDialog

图:GtkColorSelectionDialog

本章是关于 GTK+ 中的对话框的。

Windows API 控件 I

原文: http://zetcode.com/gui/winapi/controls/

控件是 Windows 应用的基本构建块。 (控件在 UNIX 中称为小部件。)Windows API 教程的这一部分涵盖了静态控件,按钮,复选框和编辑框。

控件也是窗口。 它们是使用CreateWindowW()CreateWindowExW()函数创建的。 这些函数分别将窗口类名称作为其第一个和第二个参数。 控件具有其特定的预定义窗口类名称; 因此,在创建控件时,我们不会调用RegisterClassW()RegisterClassExW()

静态控件

静态控件显示文本和图形。 无法选择静态控件。 它还不能具有键盘焦点。

静态文字

在第一个示例中,我们创建一个静态文本控件。

static_text.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Static Control";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Criminal",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 330, 270, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    static wchar_t *lyrics =  L"I know you told me I should stay away\n\
I know you said he's just a dog astray\n\
He is a bad boy with a tainted heart\n\
And even I know this ain't smart\n\
\n\
But mama, I'm in love with a criminal\n\
And this type of love isn't rational, it's physical\n\
Mama, please don't cry, I will be alright\n\
All reason aside, I just can't deny, love the guy\n\
";

    switch(msg) {

        case WM_CREATE:

            CreateWindowW(L"Static", lyrics, 
                WS_CHILD | WS_VISIBLE | SS_LEFT,
                20, 20, 300, 230, 
                hwnd, (HMENU) 1, NULL, NULL);
            break;

        case WM_DESTROY:

            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

该示例在窗口上显示歌曲的歌词。

CreateWindowW(L"Static", lyrics, 
    WS_CHILD | WS_VISIBLE | SS_LEFT,
    20, 20, 300, 230,
    hwnd, (HMENU) 1, NULL, NULL);
break;

静态控件是使用L"Static"类创建的。 文本以SS_LEFT样式向左对齐。

Static text control

Static text control

静态图像

第二个示例创建一个静态图像控件。

static_image.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void LoadMyImage(void);

HBITMAP hBitmap;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Static image";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0,IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Static image",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 330, 270, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

  return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    HWND hsti;

    switch(msg) {

        case WM_CREATE:

            LoadMyImage();
            hsti = CreateWindowW(L"Static", L"", 
                WS_CHILD | WS_VISIBLE | SS_BITMAP,
                5, 5, 300, 300, hwnd, (HMENU) 1, NULL, NULL);

            SendMessage(hsti, STM_SETIMAGE,
                (WPARAM) IMAGE_BITMAP, (LPARAM) hBitmap); 
            break;

        case WM_DESTROY:

            DeleteObject(hBitmap);
            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void LoadMyImage(void) {

    hBitmap = LoadImageW(NULL, L"C:\\prog\\slovakia.bmp", IMAGE_BITMAP,
        0, 0, LR_LOADFROMFILE);
}

该示例在窗口上显示了 BMP 图像。

hsti = CreateWindowW(L"Static", L"", 
        WS_CHILD | WS_VISIBLE | SS_BITMAP,
        5, 5, 300, 300, hwnd, (HMENU) 1, NULL, NULL);

SS_BITMAP常量使静态控件显示位图。

SendMessage(hsti, STM_SETIMAGE,
        (WPARAM) IMAGE_BITMAP, (LPARAM) hBitmap); 

发送STM_SETIMAGE消息以将新图像与静态控件关联。

void LoadMyImage(void) {

    hBitmap = LoadImageW(NULL, L"C:\\prog\\slovakia.bmp", IMAGE_BITMAP,
        0, 0, LR_LOADFROMFILE);
}

LoadImageW()函数从文件系统加载位图。 如果函数成功,则返回值是新加载的图像的句柄。

Static image control

Static image control

按钮

按钮是带有文本标签的简单控件。 用于触发动作。 当我们单击一个按钮时,它会向其父窗口发送WM_COMMAND消息。 wParam参数的低位字包含控件标识符。

button.c

#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

#define ID_BEEP 1
#define ID_QUIT 2

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Buttons";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Buttons",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  150, 150, 300, 200, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    switch(msg) {

        case WM_CREATE:

            CreateWindowW(L"Button", L"Beep",
                WS_VISIBLE | WS_CHILD ,
                20, 50, 80, 25, hwnd, (HMENU) ID_BEEP, NULL, NULL);

            CreateWindowW(L"Button", L"Quit",
                WS_VISIBLE | WS_CHILD ,
                120, 50, 80, 25, hwnd, (HMENU) ID_QUIT, NULL, NULL);
            break;

        case WM_COMMAND:

            if (LOWORD(wParam) == ID_BEEP) {

                MessageBeep(MB_OK);
            }

            if (LOWORD(wParam) == ID_QUIT) {

                PostQuitMessage(0);
            }

            break;

        case WM_DESTROY:

            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

在我们的示例中,我们创建了两个按钮。 一键鸣音。 另一个将关闭窗口。

CreateWindowW(L"Button", L"Beep",
    WS_VISIBLE | WS_CHILD ,
    20, 50, 80, 25, hwnd, (HMENU) ID_BEEP, NULL, NULL);

按钮控件是使用L"Button"类创建的。

case WM_COMMAND:

    if (LOWORD(wParam) == ID_BEEP) {

        MessageBeep(MB_OK);
    }

    if (LOWORD(wParam) == ID_QUIT) {

        PostQuitMessage(0);
    }

    break;

控件的 ID 在wParamLOWORD中。 根据 ID,我们称为MessageBeep()函数或PostQuitMessage()函数。

Button controls

Button controls

CheckBox

复选框控件是可以单击以打开或关闭选项的框。

checkbox.c

#include <windows.h>
#include <stdbool.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Check Box";
    wc.hInstance     = hInstance ;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Check Box",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  150, 150, 230, 150, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    bool checked = true;

    switch(msg) {

        case WM_CREATE:

            CreateWindowW(L"button", L"Show Title",
                WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
                20, 20, 185, 35, hwnd, (HMENU) 1, 
                NULL, NULL);

            CheckDlgButton(hwnd, 1, BST_CHECKED);
            break;

        case WM_COMMAND:

            checked = IsDlgButtonChecked(hwnd, 1);

            if (checked) {

                CheckDlgButton(hwnd, 1, BST_UNCHECKED);
                SetWindowTextW(hwnd, L"");

            } else {

               CheckDlgButton(hwnd, 1, BST_CHECKED);
               SetWindowTextW(hwnd, L"Check Box");
            }

            break;

       case WM_DESTROY:

            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

在我们的示例中,我们根据复选框的状态显示或隐藏窗口标题。

CreateWindowW(L"button", L"Show Title",
    WS_VISIBLE | WS_CHILD | BS_CHECKBOX,
    20, 20, 185, 35, hwnd, (HMENU) 1, 
    NULL, NULL);

复选框是一种特殊的按钮。 它是用BS_CHECKBOX标志创建的。

checked = IsDlgButtonChecked(hwnd, 1);

我们使用IsDlgButtonChecked()函数确定复选框的状态。

CheckDlgButton(hwnd, 1, BST_UNCHECKED);

我们使用CheckDlgButton()函数选中并取消选中该复选框。

SetWindowTextW(hwnd, L"");

SetWindowTextW()函数设置窗口的标题。

Checkbox control

Checkbox control

编辑控件

编辑控件是一个矩形子窗口,用于输入和编辑文本。 它可以是单行或多行。

edit.c

#include <windows.h>

#define ID_EDIT 1
#define ID_BUTTON 2

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;    
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Edit control";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Edit control",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  220, 220, 280, 200, 0, 0, hInstance, 0);  

    while (GetMessage(&msg, NULL, 0, 0)) {

      TranslateMessage(&msg);
      DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    static HWND hwndEdit;
    HWND hwndButton;

    switch(msg) {

        case WM_CREATE:

            hwndEdit = CreateWindowW(L"Edit", NULL, 
                WS_CHILD | WS_VISIBLE | WS_BORDER,
                50, 50, 150, 20, hwnd, (HMENU) ID_EDIT,
                NULL, NULL);

            hwndButton = CreateWindowW(L"button", L"Set title",
                WS_VISIBLE | WS_CHILD, 50, 100, 80, 25,
                hwnd, (HMENU) ID_BUTTON, NULL, NULL);

            break;

        case WM_COMMAND:	

            if (HIWORD(wParam) == BN_CLICKED) {

                int len = GetWindowTextLengthW(hwndEdit) + 1;
                wchar_t text[len];

                GetWindowTextW(hwndEdit, text, len);
                SetWindowTextW(hwnd, text);
            }

            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

在我们的示例中,我们有一个编辑控件和一个按钮。 我们可以将一些文本放入编辑控件中。 如果单击按钮,则输入的文本将显示在主窗口的标题栏中。

hwndEdit = CreateWindowW(L"Edit", NULL, 
    WS_CHILD | WS_VISIBLE | WS_BORDER,
    50, 50, 150, 20, hwnd, (HMENU) ID_EDIT,
    NULL, NULL);

编辑控件是使用L"Edit"窗口类创建的。 WS_BORDER窗口样式在控件周围创建细线边框。

if (HIWORD(wParam) == BN_CLICKED) {

   int len = GetWindowTextLengthW(hwndEdit) + 1;
   wchar_t text[len];

   GetWindowTextW(hwndEdit, text, len);
   SetWindowTextW(hwnd, text);
}

GetWindowTextLengthW()返回输入文本的长度。 注意,我们在长度上加 1。 这将包括零终止符。 尝试忽略它,看看会发生什么。 GetWindowTextW()从编辑控件接收文本。 该函数的第一个参数是包含文本的窗口或控件的句柄。 SetWindowTextW()设置窗口的文本。 在这种情况下,它是主窗口的标题。

Edit control

图:编辑控件

在 Windows API 教程的这一部分中,我们介绍了四个基本的 Windows 控件。

GTK+ 小部件

原文: http://zetcode.com/gui/gtk2/gtkwidgets/

在 GTK+ 编程教程的这一部分中,我们将介绍一些 GTK+ 小部件。

小部件是 GUI 应用的基本构建块。 多年来,一些小部件已成为编程工具包的标准。 例如按钮,复选框或滚动条。 GTK+ 工具箱的理念是将小部件的数量保持在最低水平。 将创建更多专门的小部件作为定制 GTK+ 小部件。

GtkButton

GtkButton是用于触发动作的简单小部件。

button.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *halign;
  GtkWidget *btn;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkButton");
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);

  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(window), halign);

  btn = gtk_button_new_with_label("Quit");
  gtk_widget_set_size_request(btn, 70, 30);

  gtk_container_add(GTK_CONTAINER(halign), btn);

  g_signal_connect(G_OBJECT(btn), "clicked", 
      G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  g_signal_connect(G_OBJECT(window), "destroy", 
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例显示了一个位于窗口左上角的按钮。 当我们单击按钮时,应用退出。

btn = gtk_button_new_with_label("Quit");

gtk_button_new_with_label()创建一个带有标签的新GtkButton

g_signal_connect(G_OBJECT(btn), "clicked", 
    G_CALLBACK(gtk_main_quit), G_OBJECT(window));

按钮的clicked信号连接到gtk_main_quit()函数,该功能终止应用。

GtkButton

图:GtkButton

GtkCheckButton

GtkCheckButton是具有两种状态的窗口小部件:打开和关闭。 接通状态通过复选标记显示。

checkbutton.c

#include <gtk/gtk.h>

void toggle_title(GtkWidget *widget, gpointer window) {

  if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
    gtk_window_set_title(window, "GtkCheckButton");     
  } else {
    gtk_window_set_title(window, "");
  }
}

int main(int argc, char** argv) {

  GtkWidget *window;
  GtkWidget *halign;
  GtkWidget *cb;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_window_set_default_size(GTK_WINDOW(window), 230, 150);
  gtk_window_set_title(GTK_WINDOW(window), "GtkCheckButton");

  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(window), halign);

  cb = gtk_check_button_new_with_label("Show title");
  gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), TRUE);

  GTK_WIDGET_UNSET_FLAGS(cb, GTK_CAN_FOCUS);
  gtk_container_add(GTK_CONTAINER(halign), cb);

  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(cb, "clicked", 
      G_CALLBACK(toggle_title), (gpointer) window);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例根据GtkCheckButton的状态显示窗口标题。

cb = gtk_check_button_new_with_label("Show title");
gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(cb), TRUE);

GtkCheckButton已创建并默认标记。 标题最初显示。

GTK_WIDGET_UNSET_FLAGS(cb, GTK_CAN_FOCUS);

此代码行禁用焦点。

if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
  gtk_window_set_title(window, "GtkCheckButton");     
} else {
  gtk_window_set_title(window, "");
}

我们根据GtkCheckButton的状态显示窗口的标题。 要设置窗口的标题,我们使用gtk_window_set_title()

GtkCheckButton

图:GtkCheckButton

GtkFrame

GtkFrame是一个带有装饰框和可选标签的垃圾桶。

frames.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *table;

  GtkWidget *frame1;
  GtkWidget *frame2;
  GtkWidget *frame3;
  GtkWidget *frame4;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 250, 250);
  gtk_window_set_title(GTK_WINDOW(window), "GtkFrame");

  gtk_container_set_border_width(GTK_CONTAINER(window), 10);

  table = gtk_table_new(2, 2, TRUE);
  gtk_table_set_row_spacings(GTK_TABLE(table), 10);
  gtk_table_set_col_spacings(GTK_TABLE(table), 10);
  gtk_container_add(GTK_CONTAINER(window), table);

  frame1 = gtk_frame_new("Shadow In");
  gtk_frame_set_shadow_type(GTK_FRAME(frame1), GTK_SHADOW_IN);
  frame2 = gtk_frame_new("Shadow Out");
  gtk_frame_set_shadow_type(GTK_FRAME(frame2), GTK_SHADOW_OUT);
  frame3 = gtk_frame_new("Shadow Etched In");
  gtk_frame_set_shadow_type(GTK_FRAME(frame3), GTK_SHADOW_ETCHED_IN);
  frame4 = gtk_frame_new("Shadow Etched Out");
  gtk_frame_set_shadow_type(GTK_FRAME(frame4), GTK_SHADOW_ETCHED_OUT);

  gtk_table_attach_defaults(GTK_TABLE(table), frame1, 0, 1, 0, 1);
  gtk_table_attach_defaults(GTK_TABLE(table), frame2, 0, 1, 1, 2);
  gtk_table_attach_defaults(GTK_TABLE(table), frame3, 1, 2, 0, 1);
  gtk_table_attach_defaults(GTK_TABLE(table), frame4, 1, 2, 1, 2);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例显示了四种不同的帧类型。 框架连接到表格容器中。

frame1 = gtk_frame_new("Shadow In");

gtk_frame_new()函数创建带有可选标签的GtkFrame

gtk_frame_set_shadow_type(GTK_FRAME(frame1), GTK_SHADOW_IN);

gtk_frame_set_shadow_type()函数设置帧的阴影类型。

GtkFrame

图:GtkFrame

GtkHScale

GtkHScale是用于从一系列值中选择一个值的水平滑块控件。

hscale.c

void value_changed(GtkRange *range, gpointer win) {

   gdouble val = gtk_range_get_value(range);
   gchar *str = g_strdup_printf("%.f", val);    
   gtk_label_set_text(GTK_LABEL(win), str);

   g_free(str);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *halign;
  GtkWidget *hbox;
  GtkWidget *hscale;
  GtkWidget *label;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 250);
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);
  gtk_window_set_title(GTK_WINDOW(window), "GtkHScale");

  hbox = gtk_hbox_new(FALSE, 20);

  hscale = gtk_hscale_new_with_range(0, 100, 1);
  gtk_scale_set_draw_value(GTK_SCALE(hscale), FALSE);
  gtk_widget_set_size_request(hscale, 150, -1);
  label = gtk_label_new("...");
  gtk_misc_set_alignment(GTK_MISC(label), 0.0, 1);

  gtk_box_pack_start(GTK_BOX(hbox), hscale, FALSE, FALSE, 0);
  gtk_box_pack_start(GTK_BOX(hbox), label, FALSE, FALSE, 0);

  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), hbox);
  gtk_container_add(GTK_CONTAINER(window), halign);

  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(hscale, "value-changed",
      G_CALLBACK(value_changed), label);      

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,我们有一个水平比例小部件和一个标签小部件。 当前选择的值显示在标签中。

gdouble val = gtk_range_get_value(range);

gtk_range_get_value()函数从比例小部件中检索当前选择的值。

gchar *str = g_strdup_printf("%.f", val);    
gtk_label_set_text(GTK_LABEL(win), str);

我们使用g_strdup_printf()函数构建一个字符串值,并使用gtk_label_set_text()函数将其设置为标签。

hscale = gtk_hscale_new_with_range(0, 100, 1);

gtk_hscale_new_with_range()函数创建一个具有给定范围的新水平比例小部件。 第一个参数是最小值,第二个参数是最大值,最后一个参数是步骤。

gtk_scale_set_draw_value(GTK_SCALE(hscale), FALSE);

gtk_scale_set_draw_value()指定当前值是否在字符串旁边显示为字符串。 我们关闭该值。 相反,我们以编程方式将其设置为标签小部件。

GtkHScale

图:GtkHScale

GtkLabel

GtkLabel小部件显示文本。

label.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *label;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window), "No sleep");
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);

  label = gtk_label_new("I've always been too lame\n\
To see what's before me\n\
And I know nothing sweeter than\n\
Champaign from last New Years\n\
Sweet music in my ears\n\
And a night full of no fears\n\
\n\
But if I had one wish fulfilled tonight\n\
I'd ask for the sun to never rise\n\
If God passed a mic to me to speak\n\
I'd say \"Stay in bed, world,\n\
Sleep in peace");

  gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);
  gtk_container_add(GTK_CONTAINER(window), label);

  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例显示了歌曲的两节经文。

  label = gtk_label_new("I've always been too lame\n\
To see what's before me\n\
...

我们创建一个GtkLabel小部件。 我们可以使用换行符来创建多行文本标签。 注意转义符。 我们使用了相当长的字符串,并且我们不想将所有文本都放在一行中。 在这种情况下,我们可以使用转义符。

gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_CENTER);

gtk_label_set_justify()函数使标签中的文本对齐。 使用GTK_JUSTIFY_CENTER类型时,文本居中。

GtkLabel

图:GtkLabel

GtkLabel

GtkLabel也可以显示标记语言。 标记是 Pango 文本标记语言。

markup.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *label;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 100);
  gtk_window_set_title(GTK_WINDOW(window), "Markup label");

  gchar *str = "<b>ZetCode</b>, knowledge only matters";

  label = gtk_label_new(NULL);
  gtk_label_set_markup(GTK_LABEL(label), str);

  gtk_container_add(GTK_CONTAINER(window), label);
  gtk_widget_show(label);

  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show(window);

  gtk_main();

  return 0;
}

该示例以粗体显示了一部分文本。

gchar *str = "<b>ZetCode</b>, knowledge only matters";

这是显示的字符串。 它包含一个简单的标记。

label = gtk_label_new(NULL);

我们创建一个空标签。

gtk_label_set_markup(GTK_LABEL(label), str);

gtk_label_set_markup()解析标记的字符串,并将其属性应用于标签。

markup label

图:标记标签

在 GTK+ 教程的这一部分中,我们介绍了 GTK+ 小部件。

GTK+ 小部件 II

原文: http://zetcode.com/gui/gtk2/gtkwidgetsII/

在 GTK+ 编程教程的这一部分中,我们将继续介绍各种 GTK+ 小部件。

GktComboBoxText

GktComboBoxText是一个小部件,允许用户从选项列表中进行选择。 选项是字符串。

combobox.c

#include <gtk/gtk.h>

void combo_selected(GtkWidget *widget, gpointer window) {

  gchar *text = gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
  gtk_label_set_text(GTK_LABEL(window), text);
  g_free(text);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *combo;
  GtkWidget *label;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_title(GTK_WINDOW(window), "GtkComboBox");
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(window), 15);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);

  hbox = gtk_hbox_new(FALSE, 0);
  vbox = gtk_vbox_new(FALSE, 15);

  combo = gtk_combo_box_new_text();
  gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Ubuntu");
  gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Arch");
  gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Fedora");
  gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Mint");
  gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Gentoo");
  gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Debian");

  gtk_box_pack_start(GTK_BOX(vbox), combo, FALSE, FALSE, 0);

  label = gtk_label_new("...");
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 0);

  gtk_box_pack_start(GTK_BOX(hbox), vbox, FALSE, FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), hbox);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(combo), "changed", 
        G_CALLBACK(combo_selected), (gpointer) label);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例显示了一个组合框和一个标签。 组合框具有六个选项的列表。 这些是 Linux 发行版的名称。 标签窗口小部件显示了从组合框中选择的选项。

combo = gtk_combo_box_text_new();

gtk_combo_box_text_new()函数创建一个简单的纯文本组合框。

gtk_combo_box_append_text(GTK_COMBO_BOX(combo), "Ubuntu");

gtk_combo_box_text_append_text()函数将一个字符串附加到组合框中存储的字符串列表中。

label = gtk_label_new("-");

创建一个新的标签小部件。

gchar *text =  gtk_combo_box_get_active_text(GTK_COMBO_BOX(widget));
gtk_label_set_text(GTK_LABEL(window), text);
g_free(text);

我们得到选定的文本,并为其设置标签文本。 gtk_combo_box_get_active_text()函数在组合框中返回当前活动的字符串。 我们使用gtk_label_set_text()函数将字符串设置为标签。

GktComboBoxText

图:GktComboBoxText

GtkHSeparator

GtkHSeparator是水平分隔符。 这是一种装饰小部件。 还有一个姐妹GtkVSeparator小部件。

separator.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *label1;
  GtkWidget *label2;
  GtkWidget *hseparator;
  GtkWidget *vbox;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window), "GtkHSeparator");
  gtk_window_set_resizable(GTK_WINDOW(window), FALSE);

  gtk_container_set_border_width(GTK_CONTAINER(window), 10);

  label1 = gtk_label_new("Zinc is a moderately reactive, blue gray metal \
that tarnishes in moist air and burns in air with a bright bluish-green flame,\
giving off fumes of zinc oxide. It reacts with acids, alkalis and other non-metals.\
If not completely pure, zinc reacts with dilute acids to release hydrogen.");

  gtk_label_set_line_wrap(GTK_LABEL(label1), TRUE);

  label2 = gtk_label_new("Copper is an essential trace nutrient to all high \
plants and animals. In animals, including humans, it is found primarily in \
the bloodstream, as a co-factor in various enzymes, and in copper-based pigments. \
However, in sufficient amounts, copper can be poisonous and even fatal to organisms.");

  gtk_label_set_line_wrap(GTK_LABEL(label2), TRUE);

  vbox = gtk_vbox_new(FALSE, 10);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  hseparator = gtk_hseparator_new();

  gtk_box_pack_start(GTK_BOX(vbox), label1, FALSE, TRUE, 0);
  gtk_box_pack_start(GTK_BOX(vbox), hseparator, FALSE, TRUE, 10);
  gtk_box_pack_start(GTK_BOX(vbox), label2, FALSE, TRUE, 0);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), G_OBJECT(window));

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该代码示例显示了两种化学元素的定义; 它们由水平分隔符分隔。 这使示例在视觉上更具吸引力。

   label1 = gtk_label_new("Zinc is a moderately reactive, blue gray metal \
that tarnishes in moist air and burns in air with a bright bluish-green flame,\
giving off fumes of zinc oxide. It reacts with acids, alkalis and other non-metals.\
If not completely pure, zinc reacts with dilute acids to release hydrogen.");

我们创建第一个标签,即锌元素的定义。

gtk_label_set_line_wrap(GTK_LABEL(label2), TRUE);

如果文本超过小部件的大小,则gtk_label_set_line_wrap()函数将换行。

hseparator = gtk_hseparator_new();

gtk_hseparator_new()创建一个新的GtkHSeparator

gtk_box_pack_start(GTK_BOX(vbox), label1, FALSE, TRUE, 0);
gtk_box_pack_start(GTK_BOX(vbox), hseparator, FALSE, TRUE, 10);
gtk_box_pack_start(GTK_BOX(vbox), label2, FALSE, TRUE, 0);

我们将分隔符放在标签之间。

GtkHSeparator

图:GtkHSeparator

GtkEntry

GtkEntry是单行文本输入字段。 该小部件用于输入文本数据。

entry.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *table;

  GtkWidget *label1;
  GtkWidget *label2;
  GtkWidget *label3;

  GtkWidget *entry1;
  GtkWidget *entry2;
  GtkWidget *entry3;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window), "GtkEntry");
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);

  table = gtk_table_new(3, 2, FALSE);
  gtk_container_add(GTK_CONTAINER(window), table);

  label1 = gtk_label_new("Name");
  label2 = gtk_label_new("Age");
  label3 = gtk_label_new("Occupation");

  gtk_table_attach(GTK_TABLE(table), label1, 0, 1, 0, 1, 
      GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);
  gtk_table_attach(GTK_TABLE(table), label2, 0, 1, 1, 2, 
      GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);
  gtk_table_attach(GTK_TABLE(table), label3, 0, 1, 2, 3, 
      GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);

  entry1 = gtk_entry_new();
  entry2 = gtk_entry_new();
  entry3 = gtk_entry_new();

  gtk_table_attach(GTK_TABLE(table), entry1, 1, 2, 0, 1, 
      GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);
  gtk_table_attach(GTK_TABLE(table), entry2, 1, 2, 1, 2, 
      GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);
  gtk_table_attach(GTK_TABLE(table), entry3, 1, 2, 2, 3, 
      GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);

  gtk_widget_show_all(window);

  g_signal_connect(window, "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_main();

  return 0;
}

在我们的示例中,我们显示了三个文本条目和三个标签。

table = gtk_table_new(3, 2, FALSE);
gtk_container_add(GTK_CONTAINER(window), table);

为了组织小部件,我们使用表容器小部件。

entry1 = gtk_entry_new();

gtk_entry_new()函数创建一个新的GtkEntry

gtk_table_attach(GTK_TABLE(table), entry1, 1, 2, 0, 1, 
    GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);
gtk_table_attach(GTK_TABLE(table), entry2, 1, 2, 1, 2, 
    GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);
gtk_table_attach(GTK_TABLE(table), entry3, 1, 2, 2, 3, 
    GTK_FILL | GTK_SHRINK, GTK_FILL | GTK_SHRINK, 5, 5);

我们将这些小部件附加到表小部件。

GtkEntry

图:GtkEntry

GtkImage

GtkImage是用于显示图像的小部件。

image.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *image;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window), "Red Rock");

  image = gtk_image_new_from_file("redrock.jpg");

  gtk_container_add(GTK_CONTAINER(window), image);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在我们的示例中,我们显示了城堡的图像。

image = gtk_image_new_from_file("redrock.png");

gtk_image_new_from_file()从指定的文件名创建一个新的GtkImage。 如果找不到文件或无法加载文件,则显示的GtkImage将显示“图像损坏”图标。

gtk_container_add(GTK_CONTAINER(window), image);

图像被添加到窗口容器中。

GtkStatusbar

GtkStatusbar显示状态信息。 它位于应用窗口的底部。

statusbar.c

#include <gtk/gtk.h>

void button_pressed(GtkWidget *widget, gpointer window) {

   gchar *str;
   str = g_strdup_printf("%s button clicked", 
         gtk_button_get_label(GTK_BUTTON(widget)));

   gtk_statusbar_push(GTK_STATUSBAR(window),
         gtk_statusbar_get_context_id(GTK_STATUSBAR(window), str), str);
   g_free(str);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *hbox;
  GtkWidget *vbox;
  GtkWidget *halign;
  GtkWidget *balign;
  GtkWidget *button1;
  GtkWidget *button2;
  GtkWidget *statusbar;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "GtkStatusbar");

  vbox = gtk_vbox_new(FALSE, 0);

  hbox = gtk_hbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  halign = gtk_alignment_new(0, 0, 0, 0);
  gtk_container_add(GTK_CONTAINER(halign), hbox);
  gtk_box_pack_start(GTK_BOX(vbox), halign, TRUE, TRUE, 5);

  button1 = gtk_button_new_with_label("OK");
  gtk_widget_set_size_request(button1, 70, 30 );
  button2 = gtk_button_new_with_label("Apply");
  gtk_widget_set_size_request(button2, 70, 30 );

  gtk_box_pack_start(GTK_BOX(hbox), button1, FALSE, FALSE, 5);
  gtk_box_pack_start(GTK_BOX(hbox), button2, FALSE, FALSE, 0);

  balign = gtk_alignment_new(0, 1, 1, 0);
  statusbar = gtk_statusbar_new();
  gtk_container_add(GTK_CONTAINER(balign), statusbar);
  gtk_box_pack_start(GTK_BOX(vbox), balign, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(button1), "clicked", 
           G_CALLBACK(button_pressed), G_OBJECT(statusbar));

  g_signal_connect(G_OBJECT(button2), "clicked", 
           G_CALLBACK(button_pressed), G_OBJECT(statusbar));

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在代码示例中,有两个按钮和一个状态栏。 如果单击按钮,状态栏中将显示一条消息。 它说按下了哪个按钮。

gchar *str;
str = g_strdup_printf("Button %s clicked", 
      gtk_button_get_label(GTK_BUTTON(widget)));

该消息是使用g_strdup_printf()函数构建的。 我们使用gtk_button_get_label()函数获得按钮的标签。

gtk_statusbar_push(GTK_STATUSBAR(window),
     gtk_statusbar_get_context_id(GTK_STATUSBAR(window), str), str);

我们在状态栏中显示该消息。 gtk_statusbar_push()函数将新消息推送到状态栏的栈上。 该函数需要一个上下文 ID,该上下文 ID 由gtk_statusbar_get_context_id()函数返回。

statusbar = gtk_statusbar_new();

gtk_statusbar_new()函数创建一个新的GtkStatusbar小部件。

GtkStatusbar

图:GtkStatusbar

GtkIconView

GtkIconView是一个小部件,在网格中显示图标列表。 它使用GtkListStore存储其数据。

iconview.c

#include <gtk/gtk.h>
#include <assert.h>

enum {

  COL_DISPLAY_NAME,
  COL_PIXBUF,
  NUM_COLS
};

GtkTreeModel *init_model(void) {

  GtkListStore *list_store;
  GdkPixbuf *p1, *p2, *p3, *p4;
  GtkTreeIter iter;
  GError *err = NULL;

  p1 = gdk_pixbuf_new_from_file("ubuntu.png", &err); 
  p2 = gdk_pixbuf_new_from_file("gnumeric.png", &err);
  p3 = gdk_pixbuf_new_from_file("blender.png", &err);
  p4 = gdk_pixbuf_new_from_file("inkscape.png", &err);

  assert(err==NULL);    

  list_store = gtk_list_store_new(NUM_COLS, 
      G_TYPE_STRING, GDK_TYPE_PIXBUF);

  gtk_list_store_append(list_store, &iter);
  gtk_list_store_set(list_store, &iter, COL_DISPLAY_NAME, 
      "Ubuntu", COL_PIXBUF, p1, -1);
  gtk_list_store_append(list_store, &iter);
  gtk_list_store_set(list_store, &iter, COL_DISPLAY_NAME, 
      "Gnumeric", COL_PIXBUF, p2, -1);
  gtk_list_store_append(list_store, &iter);
  gtk_list_store_set(list_store, &iter, COL_DISPLAY_NAME, 
      "Blender", COL_PIXBUF, p3, -1);
  gtk_list_store_append(list_store, &iter);
  gtk_list_store_set(list_store, &iter, COL_DISPLAY_NAME, 
      "Inkscape", COL_PIXBUF, p4, -1);

  g_object_unref(p1);
  g_object_unref(p2);
  g_object_unref(p3);
  g_object_unref(p4);    

  return GTK_TREE_MODEL(list_store);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *icon_view;
  GtkWidget *sw;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  gtk_window_set_title(GTK_WINDOW(window), "IconView");
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);
  gtk_window_set_default_size(GTK_WINDOW(window), 350, 300);

  sw = gtk_scrolled_window_new(NULL, NULL);
  gtk_container_add(GTK_CONTAINER(window), sw);
  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
      GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
      GTK_SHADOW_IN);

  icon_view = gtk_icon_view_new_with_model(init_model());
  gtk_container_add(GTK_CONTAINER(sw), icon_view);

  gtk_icon_view_set_text_column(GTK_ICON_VIEW(icon_view),
      COL_DISPLAY_NAME);
  gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(icon_view), COL_PIXBUF);
  gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(icon_view), 
      GTK_SELECTION_MULTIPLE);

  g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例显示 4 个图标。 这些图标代表四个著名的开源项目。

p1 = gdk_pixbuf_new_from_file("ubuntu.png", &err); 
p2 = gdk_pixbuf_new_from_file("gnumeric.png", &err);
p3 = gdk_pixbuf_new_from_file("blender.png", &err);
p4 = gdk_pixbuf_new_from_file("inkscape.png", &err);

我们使用gdk_pixbuf_new_from_file()函数从磁盘加载四个图像。

list_store = gtk_list_store_new(NUM_COLS, 
    G_TYPE_STRING, GDK_TYPE_PIXBUF);

gtk_list_store_new()函数创建一个GtkListStore,它是GtkTreeViewGtkIconView小部件的列表模型。 我们存储文本和pixbuf数据。

gtk_list_store_append(list_store, &iter);
gtk_list_store_set(list_store, &iter, COL_DISPLAY_NAME, 
    "ubuntu", COL_PIXBUF, p1, -1);

此代码将新行添加到模型中。

icon_view = gtk_icon_view_new_with_model(init_model());

gtk_icon_view_new_with_model()使用GtkTreeModel创建一个新的GtkIconView小部件。

gtk_container_add(GTK_CONTAINER(sw), icon_view);

GtkIconView是一个容器小部件。 我们将其添加到GtkScrolledWindow中。

gtk_icon_view_set_text_column(GTK_ICON_VIEW(icon_view),
    COL_DISPLAY_NAME);

gtk_icon_view_set_text_column()函数设置哪一列是字符串列。

gtk_icon_view_set_pixbuf_column(GTK_ICON_VIEW(icon_view),
    COL_PIXBUF);

gtk_icon_view_set_pixbuf_column()函数设置具有pixbufs的列。

gtk_icon_view_set_selection_mode(GTK_ICON_VIEW(icon_view), 
    GTK_SELECTION_MULTIPLE);

gtk_icon_view_set_selection_mode()设置GtkIconView的选择模式。 选择GTK_SELECTION_MULTIPLE模式,可以选择多个图标。

IconView

图:图标 View

在 GTK+ 教程的这一部分中,我们将继续介绍 GTK+ 小部件。

GtkTreeView小部件

原文: http://zetcode.com/gui/gtk2/gtktreeview/

在 GTK+ 编程教程的这一部分中,我们使用GtkTreeView小部件。

GtkTreeView小部件是一个复杂的小部件,可用于显示列表和树。 小部件可以具有一列或多列。 GtkTreeView小部件具有 MVC(模型视图控制器)设计架构。 这意味着数据与视图是分开的。

GtkTreeView小部件还可以使用其他几个对象。 GtkCellRenderer确定如何在GtkTreeViewColumn中显示数据。 GtkListStoreGtkTreeStore代表模型。 它们处理在GtkTreeView小部件中显示的数据。 GtkTreeIter是用于引用GtkTreeView中的行的结构。 GtkTreeSelection是处理选择的对象。

ListView

第一个示例将显示一个简单的列表视图。 我们将显示文本数据。

listview.c

#include <gtk/gtk.h>

enum {

  LIST_ITEM = 0,
  N_COLUMNS
};

void init_list(GtkWidget *list) {

  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;
  GtkListStore *store;

  renderer = gtk_cell_renderer_text_new ();
  column = gtk_tree_view_column_new_with_attributes("List Items",
          renderer, "text", LIST_ITEM, NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);

  store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);

  gtk_tree_view_set_model(GTK_TREE_VIEW(list), 
      GTK_TREE_MODEL(store));

  g_object_unref(store);
}

void add_to_list(GtkWidget *list, const gchar *str) {

  GtkListStore *store;
  GtkTreeIter iter;

  store = GTK_LIST_STORE(gtk_tree_view_get_model
      (GTK_TREE_VIEW(list)));

  gtk_list_store_append(store, &iter);
  gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);
}

void on_changed(GtkWidget *widget, gpointer label) {

  GtkTreeIter iter;
  GtkTreeModel *model;
  gchar *value;

  if (gtk_tree_selection_get_selected(
      GTK_TREE_SELECTION(widget), &model, &iter)) {

    gtk_tree_model_get(model, &iter, LIST_ITEM, &value,  -1);
    gtk_label_set_text(GTK_LABEL(label), value);
    g_free(value);
  }
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *list;

  GtkWidget *vbox;
  GtkWidget *label;
  GtkTreeSelection *selection; 

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  list = gtk_tree_view_new();

  gtk_window_set_title(GTK_WINDOW(window), "List view");
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER(window), 10);
  gtk_window_set_default_size(GTK_WINDOW(window), 270, 250);

  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);

  vbox = gtk_vbox_new(FALSE, 0);

  gtk_box_pack_start(GTK_BOX(vbox), list, TRUE, TRUE, 5);

  label = gtk_label_new("");
  gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);

  gtk_container_add(GTK_CONTAINER(window), vbox);

  init_list(list);
  add_to_list(list, "Aliens");
  add_to_list(list, "Leon");
  add_to_list(list, "The Verdict");
  add_to_list(list, "North Face");
  add_to_list(list, "Der Untergang");

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));

  g_signal_connect(selection, "changed", 
      G_CALLBACK(on_changed), label);

  g_signal_connect(G_OBJECT (window), "destroy",
      G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在我们的代码示例中,我们在GtkTreeView中显示了五个项目。 我们只有一列,并且该列的标题是隐藏的。 我们将GtkVBox放入窗口。 该框具有两个小部件:GtkTreeViewGtkLabel

list = gtk_tree_view_new();

gtk_tree_view_new()函数创建一个新的GtkTreeView小部件。

gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);

我们使用gtk_tree_view_set_headers_visible()函数隐藏列标题。

label = gtk_label_new("");
gtk_box_pack_start(GTK_BOX(vbox), label, FALSE, FALSE, 5);

创建GtkLabel并将其放置在GtkTreeView下方。

init_list(list);

此函数初始化列表。

renderer = gtk_cell_renderer_text_new();
column = gtk_tree_view_column_new_with_attributes("List Items",
        renderer, "text", LIST_ITEM, NULL);
gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);

在该函数内部,我们创建并附加一列。

store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);

gtk_tree_view_set_model(GTK_TREE_VIEW(list), 
    GTK_TREE_MODEL(store));

我们创建一个GtkListStore(模型)并将其设置为列表。

g_object_unref(store);

TreeView增加了存储对象的引用。 我们使用g_object_unref()函数将引用从 2 减少到 1。 然后随视图自动销毁模型。

add_to_list(list, "Aliens");

该用户功能将一个选项添加到列表中。

store = GTK_LIST_STORE(gtk_tree_view_get_model
    (GTK_TREE_VIEW(list)));

gtk_list_store_append(store, &iter);
gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);

add_to_list()函数内部,我们使用gtk_tree_view_get_model()函数调用获得模型。 我们附加一个新行,并为该行设置一个值,该值由GtkTreeIter对象引用。

selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));

不需要显式创建GtkTreeSelection; 它是使用GtkTreeView小部件自动创建的。 使用gtk_tree_view_get_selection()函数调用可获得对窗口小部件的引用。

g_signal_connect(selection, "changed", 
    G_CALLBACK(on_changed), label);

GtkTreeSelectionchanged信号连接到on_changed()处理器。

if (gtk_tree_selection_get_selected(
    GTK_TREE_SELECTION(widget), &model, &iter)) {

gtk_tree_selection_get_selected()函数将iter设置为当前选定的节点。

gtk_tree_model_get(model, &iter, LIST_ITEM, &value,  -1);

在处理函数中,我们获取iter对象引用的行中单元格的值。

gtk_label_set_text(GTK_LABEL(label), value);

检索到的值通过gtk_label_set_text()函数设置为标签。

List view

图:列表视图

动态列表视图

第二个示例在前一个示例中添加了其他功能。 我们将能够在列表视图中添加和删除项目。

dynamiclistview.c

#include <gtk/gtk.h>

enum {

  LIST_ITEM = 0,
  N_COLUMNS
};

GtkWidget *list;

void append_item(GtkWidget *widget, gpointer entry) {

  GtkListStore *store;
  GtkTreeIter iter;

  const gchar *str = gtk_entry_get_text(entry); 

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));

  gtk_list_store_append(store, &iter);
  gtk_list_store_set(store, &iter, LIST_ITEM, str, -1);

  gtk_entry_set_text(entry, "");
}

void remove_item(GtkWidget *widget, gpointer selection) {

  GtkListStore *store;
  GtkTreeModel *model;
  GtkTreeIter  iter;

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));

  if (gtk_tree_model_get_iter_first(model, &iter) == FALSE) {
      return;
  }

  if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), 
         &model, &iter)) {
    gtk_list_store_remove(store, &iter);
  }
}

void remove_all(GtkWidget *widget, gpointer selection) {

  GtkListStore *store;
  GtkTreeModel *model;
  GtkTreeIter  iter;

  store = GTK_LIST_STORE(gtk_tree_view_get_model(GTK_TREE_VIEW(list)));
  model = gtk_tree_view_get_model(GTK_TREE_VIEW(list));

  if (gtk_tree_model_get_iter_first(model, &iter) == FALSE) {
      return;
  }

  gtk_list_store_clear(store);
}

void init_list(GtkWidget *list) {

  GtkCellRenderer    *renderer;
  GtkTreeViewColumn  *column;
  GtkListStore       *store;

  renderer = gtk_cell_renderer_text_new();
  column = gtk_tree_view_column_new_with_attributes("List Item",
          renderer, "text", LIST_ITEM, NULL);
  gtk_tree_view_append_column(GTK_TREE_VIEW(list), column);

  store = gtk_list_store_new(N_COLUMNS, G_TYPE_STRING);

  gtk_tree_view_set_model(GTK_TREE_VIEW(list), GTK_TREE_MODEL(store));

  g_object_unref(store);
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *sw;

  GtkWidget *remove;
  GtkWidget *add;
  GtkWidget *removeAll;
  GtkWidget *entry;

  GtkWidget *vbox;
  GtkWidget *hbox;

  GtkTreeSelection *selection; 

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);

  gtk_window_set_title(GTK_WINDOW(window), "List view");
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_container_set_border_width(GTK_CONTAINER (window), 10);
  gtk_widget_set_size_request(window, 370, 270);

  sw = gtk_scrolled_window_new(NULL, NULL);
  list = gtk_tree_view_new();  
  gtk_container_add(GTK_CONTAINER(sw), list);

  gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(sw),
            GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);

  gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(sw),
            GTK_SHADOW_ETCHED_IN);

  gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(list), FALSE);

  vbox = gtk_vbox_new(FALSE, 0);

  gtk_box_pack_start(GTK_BOX(vbox), sw, TRUE, TRUE, 5);

  hbox = gtk_hbox_new(FALSE, 5);

  add = gtk_button_new_with_label("Add");
  remove = gtk_button_new_with_label("Remove");
  removeAll = gtk_button_new_with_label("Remove All");
  entry = gtk_entry_new();
  gtk_widget_set_size_request(entry, 120, -1);

  gtk_box_pack_start(GTK_BOX(hbox), add, FALSE, TRUE, 3);
  gtk_box_pack_start(GTK_BOX(hbox), entry, FALSE, TRUE, 3);
  gtk_box_pack_start(GTK_BOX(hbox), remove, FALSE, TRUE, 3);
  gtk_box_pack_start(GTK_BOX(hbox), removeAll, FALSE, TRUE, 3);

  gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, TRUE, 3);

  gtk_container_add(GTK_CONTAINER(window), vbox);

  init_list(list);

  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(list));

  g_signal_connect(G_OBJECT(add), "clicked",
          G_CALLBACK(append_item), entry);

  g_signal_connect(G_OBJECT(remove), "clicked",
          G_CALLBACK(remove_item), selection);

  g_signal_connect(G_OBJECT(removeAll), "clicked",
          G_CALLBACK(remove_all), selection);

  g_signal_connect(G_OBJECT(window), "destroy",
          G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,我们有三个按钮和一个文本输入。 这些按钮添加一个新项目,删除选定的项目,然后删除所有项目。

sw = gtk_scrolled_window_new(NULL, NULL);
list = gtk_tree_view_new();  
gtk_container_add(GTK_CONTAINER(sw), list);

GtkTreeView放置在滚动窗口内。

if (gtk_tree_selection_get_selected(GTK_TREE_SELECTION(selection), 
    &model, &iter)) {
  gtk_list_store_remove(store, &iter);
}

gtk_list_store_remove()函数从列表中删除一个项目。

gtk_list_store_clear(store);

gtk_list_store_clear()从列表中删除所有项目。

if (gtk_tree_model_get_iter_first(model, &iter) == FALSE) {
    return;
}

此代码检查列表中是否还有剩余项目。 显然,只有列表中至少剩余一个,我们才能删除项目。

Dynamic List view

图:动态列表视图

TreeView

以下示例使用GtkTreeView小部件显示分层数据。 在前两个示例中,我们使用了列表视图。 现在我们将使用树形视图。

treeview.c

#include <gtk/gtk.h>

enum {
  COLUMN = 0,
  NUM_COLS
};

void on_changed(GtkWidget *widget, gpointer statusbar) {

  GtkTreeIter iter;
  GtkTreeModel *model;
  gchar *value;

  if (gtk_tree_selection_get_selected(
      GTK_TREE_SELECTION(widget), &model, &iter)) {

    gtk_tree_model_get(model, &iter, COLUMN, &value,  -1);
    gtk_statusbar_push(GTK_STATUSBAR(statusbar),
        gtk_statusbar_get_context_id(GTK_STATUSBAR(statusbar), 
            value), value);
    g_free(value);
  }
}

GtkTreeModel *create_and_fill_model(void) {

  GtkTreeStore *treestore;
  GtkTreeIter toplevel, child;

  treestore = gtk_tree_store_new(NUM_COLS,
                  G_TYPE_STRING);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel,
                     COLUMN, "Scripting languages",
                     -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COLUMN, "Python",
                     -1);
  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COLUMN, "Perl",
                     -1);
  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COLUMN, "PHP",
                     -1);

  gtk_tree_store_append(treestore, &toplevel, NULL);
  gtk_tree_store_set(treestore, &toplevel,
                     COLUMN, "Compiled languages",
                     -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COLUMN, "C",
                     -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COLUMN, "C++",
                     -1);

  gtk_tree_store_append(treestore, &child, &toplevel);
  gtk_tree_store_set(treestore, &child,
                     COLUMN, "Java",
                     -1);

  return GTK_TREE_MODEL(treestore);
}

GtkWidget *create_view_and_model(void) {

  GtkTreeViewColumn *col;
  GtkCellRenderer *renderer;
  GtkWidget *view;
  GtkTreeModel *model;

  view = gtk_tree_view_new();

  col = gtk_tree_view_column_new();
  gtk_tree_view_column_set_title(col, "Programming languages");
  gtk_tree_view_append_column(GTK_TREE_VIEW(view), col);

  renderer = gtk_cell_renderer_text_new();
  gtk_tree_view_column_pack_start(col, renderer, TRUE);
  gtk_tree_view_column_add_attribute(col, renderer, 
      "text", COLUMN);

  model = create_and_fill_model();
  gtk_tree_view_set_model(GTK_TREE_VIEW(view), model);
  g_object_unref(model); 

  return view;
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *view;
  GtkTreeSelection *selection; 
  GtkWidget *vbox;
  GtkWidget *statusbar;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_title(GTK_WINDOW(window), "Tree view");
  gtk_widget_set_size_request(window, 350, 300);

  vbox = gtk_vbox_new(FALSE, 2);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  view = create_view_and_model();
  selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(view));

  gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 1);

  statusbar = gtk_statusbar_new();
  gtk_box_pack_start(GTK_BOX(vbox), statusbar, FALSE, TRUE, 1);

  g_signal_connect(selection, "changed", 
      G_CALLBACK(on_changed), statusbar);

  g_signal_connect (G_OBJECT (window), "destroy",
          G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在示例中,我们将编程语言分为两组:脚本语言和编译语言。 语言类别是其项目列表的顶级节点。 当前选择的项目显示在状态栏中。 创建树视图的步骤与创建列表视图非常相似。

treestore = gtk_tree_store_new(NUM_COLS,
                G_TYPE_STRING);

gtk_tree_store_new()函数创建GtkTreeStore,它是与GtkTreeView一起使用的树状数据结构。

gtk_tree_store_append(treestore, &toplevel, NULL);
gtk_tree_store_set(treestore, &toplevel,
                  COLUMN, "Scripting languages",
                  -1);

这两行创建一个顶级节点。

gtk_tree_store_append(treestore, &child, &toplevel);
gtk_tree_store_set(treestore, &child,
                  COLUMN, "Python",
                  -1);

在这里,我们将一个子项添加到顶级节点。

Tree View

图:树形视图

在本章中,我们介绍了GtkTreeView小部件。

GtkTextView小部件

原文: http://zetcode.com/gui/gtk2/gtktextview/

在 GTK+ 编程教程的这一部分中,我们使用GtkTextView小部件。

GtkTextView小部件用于显示和编辑多行文本。 GtkTextView小部件也具有 MVC 设计。 GtkTextView代表视图组件,GtkTextBuffer代表模型组件。 GtkTextBuffer用于处理文本数据。 GtkTextTag是可以应用于文本的属性。 GtkTextIter表示文本中两个字符之间的位置。 所有使用文本的操作都是使用文本迭代器完成的。

简单的例子

在第一个示例中,我们显示一些GtkTextView's函数。 我们展示了如何将各种文本标签应用于文本数据。

simpletextview.c

#include <gtk/gtk.h>

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *view;
  GtkWidget *vbox;

  GtkTextBuffer *buffer;
  GtkTextIter start, end;
  GtkTextIter iter;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 300, 200);
  gtk_window_set_title(GTK_WINDOW(window), "GtkTextView");

  vbox = gtk_vbox_new(FALSE, 0);
  view = gtk_text_view_new();
  gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 0);

  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));

  gtk_text_buffer_create_tag(buffer, "gap",
        "pixels_above_lines", 30, NULL);

  gtk_text_buffer_create_tag(buffer, "lmarg", 
      "left_margin", 5, NULL);
  gtk_text_buffer_create_tag(buffer, "blue_fg", 
      "foreground", "blue", NULL); 
  gtk_text_buffer_create_tag(buffer, "gray_bg", 
      "background", "gray", NULL); 
  gtk_text_buffer_create_tag(buffer, "italic", 
      "style", PANGO_STYLE_ITALIC, NULL);
  gtk_text_buffer_create_tag(buffer, "bold", 
      "weight", PANGO_WEIGHT_BOLD, NULL);

  gtk_text_buffer_get_iter_at_offset(buffer, &iter, 0);

  gtk_text_buffer_insert(buffer, &iter, "Plain text\n", -1);
  gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, 
        "Colored Text\n", -1, "blue_fg", "lmarg",  NULL);
  gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, 
        "Text with colored background\n", -1, "lmarg", "gray_bg", NULL);

  gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, 
        "Text in italics\n", -1, "italic", "lmarg",  NULL);

  gtk_text_buffer_insert_with_tags_by_name (buffer, &iter, 
        "Bold text\n", -1, "bold", "lmarg",  NULL);

  gtk_container_add(GTK_CONTAINER(window), vbox);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

该示例显示了一些应用了不同GtkTextTags的文本。

view = gtk_text_view_new();

gtk_text_view_new()函数创建一个新的GtkTextView小部件。

gtk_text_buffer_create_tag(buffer, "blue_fg", 
    "foreground", "blue", NULL); 

gtk_text_buffer_create_tag()函数创建一个标签,并将其添加到缓冲区的标签表中。 第二个参数是标签名称。 标签将文本的颜色更改为蓝色。

gtk_text_buffer_insert_with_tags_by_name(buffer, &iter, 
      "Colored Text\n", -1, "blue_fg", "lmarg",  NULL);

gtk_text_buffer_insert_with_tags_by_name()函数插入带有blue_fglmarg文本标签的文本。 标签通过其名称识别。

GtkTextView

图:GtkTextView

行和列

下面的示例显示文本光标的当前行和列。

linescols.c

#include <gtk/gtk.h>

update_statusbar(GtkTextBuffer *buffer,
                  GtkStatusbar  *statusbar) {
  gchar *msg;
  gint row, col;
  GtkTextIter iter;

  gtk_statusbar_pop(statusbar, 0); 

  gtk_text_buffer_get_iter_at_mark(buffer,
      &iter, gtk_text_buffer_get_insert(buffer));

  row = gtk_text_iter_get_line(&iter);
  col = gtk_text_iter_get_line_offset(&iter);

  msg = g_strdup_printf("Col: %d Ln: %d", col+1, row+1);

  gtk_statusbar_push(statusbar, 0, msg);

  g_free(msg);
}

void mark_set_callback(GtkTextBuffer *buffer, 
    const GtkTextIter *new_location, GtkTextMark *mark, gpointer data) {

  update_statusbar(buffer, GTK_STATUSBAR(data));
}

int main(int argc, char *argv[]) {

  GtkWidget *window;
  GtkWidget *vbox;

  GtkWidget *toolbar;
  GtkWidget *view;
  GtkWidget *statusbar;
  GtkToolItem *exit;
  GtkTextBuffer *buffer;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 350, 300);
  gtk_window_set_title(GTK_WINDOW(window), "Lines & columns");

  vbox = gtk_vbox_new(FALSE, 0);
  gtk_container_add(GTK_CONTAINER(window), vbox);

  toolbar = gtk_toolbar_new();
  gtk_toolbar_set_style(GTK_TOOLBAR(toolbar), GTK_TOOLBAR_ICONS);

  exit = gtk_tool_button_new_from_stock(GTK_STOCK_QUIT);
  gtk_toolbar_insert(GTK_TOOLBAR(toolbar), exit, -1);

  gtk_box_pack_start(GTK_BOX(vbox), toolbar, FALSE, FALSE, 5);

  view = gtk_text_view_new();
  gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(view), GTK_WRAP_WORD);
  gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 0);
  gtk_widget_grab_focus(view);

  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));

  statusbar = gtk_statusbar_new();
  gtk_box_pack_start(GTK_BOX(vbox), statusbar, FALSE, FALSE, 0);

  g_signal_connect(G_OBJECT(exit), "clicked", 
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(buffer, "changed",
        G_CALLBACK(update_statusbar), statusbar);

  g_signal_connect_object(buffer, "mark_set", 
        G_CALLBACK(mark_set_callback), statusbar, 0);

  g_signal_connect_swapped(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  gtk_widget_show_all(window);

  update_statusbar(buffer, GTK_STATUSBAR(statusbar));

  gtk_main();

  return 0;
}

在此代码示例中,我们在状态栏中显示文本光标的当前位置。

g_signal_connect(buffer, "changed",
      G_CALLBACK(update_statusbar), statusbar);

更改文本时,我们将调用update_statusbar()处理器。

g_signal_connect_object(buffer, "mark_set", 
      G_CALLBACK(mark_set_callback), statusbar, 0);

光标移动时会发出mark_set信号。

gtk_statusbar_pop(statusbar, 0); 

此代码行从状态栏中清除上下文标识为 0 的消息。

gtk_text_buffer_get_iter_at_mark(buffer,
    &iter, gtk_text_buffer_get_insert(buffer));

row = gtk_text_iter_get_line(&iter);
col = gtk_text_iter_get_line_offset(&iter);

这些行确定当前行和列。

msg = g_strdup_printf("Col %d Ln %d", col+1, row+1);

g_strdup_printf()用于构建要在状态栏上显示的文本。

gtk_statusbar_push(statusbar, 0, msg);

我们使用gtk_statusbar_push()函数在状态栏上显示文本。

Lines and columns

图:直线 and columns

搜索和高亮

在下一个示例中,我们在GtkTextBuffer中进行了一些搜索; 我们在文本缓冲区中高亮一些文本模式。

search.c

#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>

gboolean key_pressed(GtkWidget *window,
    GdkEventKey* event, GtkTextBuffer *buffer) {

  GtkTextIter start_sel, end_sel;
  GtkTextIter start_find, end_find;
  GtkTextIter start_match, end_match;
  gboolean selected;    
  gchar *text;            

  if ((event->type == GDK_KEY_PRESS) && 
     (event->state & GDK_CONTROL_MASK)) {

    switch (event->keyval) {

      case GDK_m :
        selected = gtk_text_buffer_get_selection_bounds(buffer, 
            &start_sel, &end_sel);
      if (selected) {
        gtk_text_buffer_get_start_iter(buffer, &start_find);
        gtk_text_buffer_get_end_iter(buffer, &end_find);

        gtk_text_buffer_remove_tag_by_name(buffer, "gray_bg", 
            &start_find, &end_find);  
        text = (gchar *) gtk_text_buffer_get_text(buffer, &start_sel,
            &end_sel, FALSE);

        while (gtk_text_iter_forward_search(&start_find, text, 
                GTK_TEXT_SEARCH_TEXT_ONLY | 
                GTK_TEXT_SEARCH_VISIBLE_ONLY, 
                &start_match, &end_match, NULL)) {

          gtk_text_buffer_apply_tag_by_name(buffer, "gray_bg", 
              &start_match, &end_match);
          gint offset = gtk_text_iter_get_offset(&end_match);
          gtk_text_buffer_get_iter_at_offset(buffer, 
              &start_find, offset);
        }

        g_free(text);
      }

      break;

      case GDK_r:
        gtk_text_buffer_get_start_iter(buffer, &start_find);
        gtk_text_buffer_get_end_iter(buffer, &end_find);

        gtk_text_buffer_remove_tag_by_name(buffer, "gray_bg", 
            &start_find, &end_find);  
      break;
    }
  }

  return FALSE;
}

int main(int argc, gchar *argv[]) {

  GtkWidget *window;
  GtkWidget *view;
  GtkWidget *vbox;

  GtkTextBuffer *buffer;
  GtkTextIter start, end;
  GtkTextIter iter;

  gtk_init(&argc, &argv);

  window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
  gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
  gtk_window_set_default_size(GTK_WINDOW(window), 350, 300);
  gtk_window_set_title(GTK_WINDOW(window), "Search & highlight");
  GTK_WINDOW(window)->allow_shrink = TRUE;

  vbox = gtk_vbox_new(FALSE, 0);
  view = gtk_text_view_new();
  gtk_widget_add_events(view, GDK_BUTTON_PRESS_MASK);
  gtk_box_pack_start(GTK_BOX(vbox), view, TRUE, TRUE, 0);

  buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(view));
  gtk_text_buffer_create_tag(buffer, "gray_bg", 
      "background", "lightgray", NULL); 
  gtk_container_add(GTK_CONTAINER(window), vbox);

  g_signal_connect(G_OBJECT(window), "destroy",
        G_CALLBACK(gtk_main_quit), NULL);

  g_signal_connect(G_OBJECT(window), "key-press-event",
        G_CALLBACK(key_pressed), buffer);

  gtk_widget_show_all(window);

  gtk_main();

  return 0;
}

在我们的代码示例中,我们使用键盘快捷键。 Ctrl + M 快捷键高亮所有当前选中的文本。 Ctrl + R 从文本中删除高亮的内容。

gtk_text_buffer_create_tag(buffer, "gray_bg", 
    "background", "lightgray", NULL); 

这是我们在示例中使用的GtkTextTag。 标签将文本的背景设为灰色。

selected = gtk_text_buffer_get_selection_bounds(buffer, 
    &start_sel, &end_sel);

使用gtk_text_buffer_get_selection_bounds()函数,我们可以获得所选文本的开始和结束位置。

gtk_text_buffer_get_start_iter(buffer, &start_find);
gtk_text_buffer_get_end_iter(buffer, &end_find);

我们获得文本缓冲区中的第一个和最后一个位置。

gtk_text_buffer_remove_tag_by_name(buffer, "gray_bg", 
    &start_find, &end_find);  

使用gtk_text_buffer_remove_tag_by_name()函数,我们可以删除所有以前的文本标签。

text = (gchar *) gtk_text_buffer_get_text(buffer, &start_sel,
    &end_sel, FALSE);

我们获得选定的文本。 这是我们要搜索的文本。

while (gtk_text_iter_forward_search(&start_find, text, 
        GTK_TEXT_SEARCH_TEXT_ONLY | 
        GTK_TEXT_SEARCH_VISIBLE_ONLY, 
        &start_match, &end_match, NULL)) {

    gtk_text_buffer_apply_tag_by_name(buffer, "gray_bg", 
        &start_match, &end_match);
    gint offset = gtk_text_iter_get_offset(&end_match);
    gtk_text_buffer_get_iter_at_offset(buffer, 
        &start_find, offset);
}

此代码搜索所有出现的所选文本。 如果找到任何匹配项,则应用文本标签。 匹配之后,单词的终点成为下一次搜索的起点。

Search & Highlight

图:搜索和突出显示

在本章中,我们介绍了GtkTextView小部件。

自定义 GTK+ 小部件

原文: http://zetcode.com/gui/gtk2/customwidget/

在 GTK+ 编程教程的这一部分中,我们创建一个自定义的 GTK+ 小部件。 我们使用 Cario 图形库。

CPU 小部件

在下一个示例中,我们创建一个自定义 CPU 小部件。

mycpu.h

#ifndef __MY_CPU_H__
#define __MY_CPU_H__

#include <gtk/gtk.h>
#include <cairo.h>

G_BEGIN_DECLS

/* Standart GObject macros */
#define MY_TYPE_CPU (my_cpu_get_type())
#define MY_CPU(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), MY_TYPE_CPU, MyCpu))
#define MY_CPU_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), MY_TYPE_CPU, MyCpuClass))
#define MY_IS_CPU(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), MY_TYPE_CPU))
#define MY_IS_CPU_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), MY_TYPE_CPU))
#define MY_CPU_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), MY_TYPE_CPU, MyCpuClass))

/* Type definition */
typedef struct _MyCpu        MyCpu;
typedef struct _MyCpuClass   MyCpuClass;
typedef struct _MyCpuPrivate MyCpuPrivate;

struct _MyCpu {

   GtkWidget parent;

   /*< Private >*/
   MyCpuPrivate *priv;
};

struct _MyCpuClass {

   GtkWidgetClass parent_class;
};

/* Public API */
GType      my_cpu_get_type(void) G_GNUC_CONST;
GtkWidget *my_cpu_new(void);

gdouble my_cpu_get_percent(MyCpu *cpu);
void    my_cpu_set_percent(MyCpu *cpu, gdouble sel);

G_END_DECLS

#endif /* __MY_CPU_H__ */

mycpu.h文件中,我们定义了自定义窗口小部件的类型,宏和函数。

mycpu.c

/* mycpu.c */

#include "mycpu.h"

/* Properties enum */
enum {

   P_0, /* Padding */
   P_PERCENT
};

/* Private data structure */
struct _MyCpuPrivate {

   gdouble percent;
   GdkWindow *window;
};

const gint WIDTH = 80;
const gint HEIGHT = 100;

/* Internal API */
static void my_cpu_set_property(GObject *object, guint prop_id, 
    const GValue *value, GParamSpec *pspec);
static void my_cpu_get_property(GObject *object, guint prop_id,
    GValue *value, GParamSpec *pspec);
static void my_cpu_size_request(GtkWidget *widget, 
    GtkRequisition *requisition);
static void my_cpu_size_allocate(GtkWidget *widget, 
    GtkAllocation *allocation);
static void my_cpu_realize(GtkWidget *widget);
static gboolean my_cpu_expose(GtkWidget *widget, 
    GdkEventExpose *event);

/* Define type */
G_DEFINE_TYPE(MyCpu, my_cpu, GTK_TYPE_WIDGET)

/* Initialization */
static void my_cpu_class_init(MyCpuClass *klass) {

   GObjectClass *g_class;
   GtkWidgetClass *w_class;
   GParamSpec *pspec;

   g_class = G_OBJECT_CLASS(klass);
   w_class = GTK_WIDGET_CLASS(klass);

   /* Override widget class methods */
   g_class->set_property  = my_cpu_set_property;
   g_class->get_property  = my_cpu_get_property;

   w_class->realize       = my_cpu_realize;
   w_class->size_request  = my_cpu_size_request;
   w_class->size_allocate = my_cpu_size_allocate;
   w_class->expose_event  = my_cpu_expose;

   /* Install property */
   pspec = g_param_spec_double("percent", "Percent", 
       "What CPU load should be displayed", 0, 1, 0, 
       G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

   g_object_class_install_property(g_class, P_PERCENT, pspec);

   /* Add private data */
   g_type_class_add_private(g_class, sizeof(MyCpuPrivate));
}

static void my_cpu_init(MyCpu *cpu) {

   MyCpuPrivate *priv;

   priv = G_TYPE_INSTANCE_GET_PRIVATE(cpu, MY_TYPE_CPU, MyCpuPrivate);

   gtk_widget_set_has_window(GTK_WIDGET(cpu), TRUE);

   /* Set default values */
   priv->percent = 0;

   /* Create cache for faster access */
   cpu->priv = priv;
}

/* Overriden virtual methods */
static void my_cpu_set_property(GObject *object, guint prop_id,
    const GValue *value, GParamSpec *pspec) {

   MyCpu *cpu = MY_CPU(object);

   switch(prop_id) {

      case P_PERCENT:

         my_cpu_set_percent(cpu, g_value_get_double(value));
         break;

      default:

         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
   }
}

static void my_cpu_get_property(GObject *object, guint prop_id,
                GValue *value, GParamSpec *pspec) {

   MyCpu *cpu = MY_CPU(object);

   switch(prop_id) {

      case P_PERCENT:
         g_value_set_double(value, cpu->priv->percent);
         break;

      default:
         G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
         break;
   }
}

static void my_cpu_realize(GtkWidget *widget) {

   MyCpuPrivate *priv = MY_CPU(widget)->priv;
   GtkAllocation alloc;
   GdkWindowAttr attrs;
   guint attrs_mask;

   gtk_widget_set_realized(widget, TRUE);

   gtk_widget_get_allocation(widget, &alloc);

   attrs.x           = alloc.x;
   attrs.y           = alloc.y;
   attrs.width       = alloc.width;
   attrs.height      = alloc.height;
   attrs.window_type = GDK_WINDOW_CHILD;
   attrs.wclass      = GDK_INPUT_OUTPUT;
   attrs.event_mask  = gtk_widget_get_events(widget) | GDK_EXPOSURE_MASK;

   attrs_mask = GDK_WA_X | GDK_WA_Y;

   priv->window = gdk_window_new(gtk_widget_get_parent_window(widget),
               &attrs, attrs_mask);
   gdk_window_set_user_data(priv->window, widget);
   gtk_widget_set_window(widget, priv->window);

   widget->style = gtk_style_attach(gtk_widget_get_style( widget ),
                             priv->window);
   gtk_style_set_background(widget->style, priv->window, GTK_STATE_NORMAL);
}

static void my_cpu_size_request(GtkWidget *widget, 
    GtkRequisition *requisition) {

   requisition->width  = WIDTH;
   requisition->height = HEIGHT;
}

static void my_cpu_size_allocate(GtkWidget *widget,
                 GtkAllocation *allocation) {

   MyCpuPrivate *priv;

   priv = MY_CPU(widget)->priv;

   gtk_widget_set_allocation(widget, allocation);

   if (gtk_widget_get_realized(widget)) {

      gdk_window_move_resize(priv->window, allocation->x, allocation->y,
          WIDTH, HEIGHT);
   }
}

static gboolean my_cpu_expose(GtkWidget *widget, 
    GdkEventExpose *event) {

   MyCpuPrivate *priv = MY_CPU(widget)->priv;
   cairo_t *cr;
   gint limit;
   gint i;

   cr = gdk_cairo_create(event->window);

   cairo_translate(cr, 0, 7);

   cairo_set_source_rgb(cr, 0, 0, 0);
   cairo_paint(cr);

   limit = 20 - priv->percent / 5;

   for (i = 1; i <= 20; i++) {

      if (i > limit) {
         cairo_set_source_rgb(cr, 0.6, 1.0, 0);
      } else {
         cairo_set_source_rgb(cr, 0.2, 0.4, 0);
      }

      cairo_rectangle(cr, 8,  i * 4, 30, 3);
      cairo_rectangle(cr, 42, i * 4, 30, 3);
      cairo_fill(cr);
   }

   cairo_destroy(cr);

   return TRUE;
}

/* Public API */
GtkWidget *my_cpu_new(void) {

   return(g_object_new(MY_TYPE_CPU, NULL));
}

gdouble my_cpu_get_percent(MyCpu *cpu) {

   g_return_val_if_fail(MY_IS_CPU(cpu), 0);

   return(cpu->priv->percent);
}

void my_cpu_set_percent(MyCpu *cpu, gdouble sel) {

   g_return_if_fail(MY_IS_CPU(cpu));

   cpu->priv->percent = sel;
   gtk_widget_queue_draw(GTK_WIDGET(cpu));
}

mycpu.c是 CPU 小部件的实现。 CPU 小部件是GtkWidget,我们可以使用 Cairo API 在其上进行绘制。 我们绘制一个黑色背景和 40 个小矩形。 矩形以两种颜色绘制:深绿色和亮绿色。 GtkVScale小部件控制在小部件上绘制的鲜绿色矩形的数量。

main.c

#include "mycpu.h"

void cb_changed(GtkRange *range, GtkWidget *cpu) {

   my_cpu_set_percent(MY_CPU(cpu), gtk_range_get_value(range));
}

int main(int argc, char *argv[]) {

   GtkWidget *window;
   GtkWidget *hbox;
   GtkWidget *vbox;
   GtkWidget *cpu;
   GtkWidget *scale;

   gtk_init(&argc, &argv);

   window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
   gtk_container_set_border_width(GTK_CONTAINER(window), 15);
   gtk_window_set_title(GTK_WINDOW(window), "CPU widget");

   vbox = gtk_vbox_new(FALSE, 0);
   hbox = gtk_hbox_new(FALSE, 25);

   cpu = my_cpu_new();
   gtk_box_pack_start(GTK_BOX(hbox), cpu, FALSE, FALSE, 0);

   scale = gtk_vscale_new_with_range(0, 100, 1);
   gtk_scale_set_draw_value(GTK_SCALE(scale), FALSE);
   gtk_range_set_inverted(GTK_RANGE(scale), TRUE);
   gtk_box_pack_start(GTK_BOX(hbox), scale, FALSE, FALSE, 0);

   gtk_box_pack_start(GTK_BOX(vbox), hbox, FALSE, FALSE, 0);
   gtk_container_add(GTK_CONTAINER(window), vbox);

   g_signal_connect(scale, "value-changed", G_CALLBACK(cb_changed), cpu);
   g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);

   gtk_widget_show_all(window);

   gtk_main();

   return 0;
}

这是主文件。 我们创建自定义 CPU 小部件,并使其与GtkVScale小部件一起使用。

gcc -Wall $(pkg-config --cflags gtk+-2.0) -o mycpu.o -c mycpu.c
gcc -Wall $(pkg-config --cflags gtk+-2.0) -o main.o -c main.c
gcc -o test_cpu main.o mycpu.o $(pkg-config --libs gtk+-2.0)

使用这些命令,我​​们构建了示例。

CPU widget

图:CPU小部件

在本章中,我们创建了一个自定义的 GTK+ 小部件。

PyQt4 教程

原文: http://zetcode.com/gui/pyqt4/

这是 PyQt4 教程。 本教程适合初学者和中级程序员。 阅读完本教程后,您将可以编写非平凡的 PyQt4 应用。 PyQt5 教程是本教程的后续版本。

目录

  • 简介
  • 第一个程序
  • 菜单和工具栏
  • 布局管理
  • 事件和信号
  • 对话框
  • 小部件
  • 小部件 II
  • 拖放
  • 绘图
  • 自定义小部件
  • 俄罗斯方块游戏

电子书

独特的电子书,涵盖了 PyQt4 库的高级功能:高级 PyQt4 教程。

Tweet

相关教程

为了让您重新了解 Python 语言,在 ZetCode 上有一个 Python 教程。 wxPython 教程, PyGTK 教程和 Tkinter 教程是其他流行的 Python GUI 绑定的教程。

PyQt4 简介

原文: http://zetcode.com/gui/pyqt4/introduction/

这是 PyQt4 入门教程。 本教程的目的是使您开始使用 PyQt4 工具包。 该教程已在 Linux 上创建并测试。

关于 PyQt4

PyQt4 是用于创建 GUI 应用的工具包。 它融合了 Python 编程语言和成功的 Qt 库。 Qt 库是功能最强大的 GUI 库之一。 PyQt4 的官方主页位于 www.riverbankcomputing.co.uk/news 上。 PyQt4 是由 Riverbank Computing 开发的。

PyQt4 被实现为一组 Python 模块。 它具有 440 个类和 6000 个函数和方法。 它是一个多平台工具包,可在所有主要操作系统(包括 Unix,Windows 和 Mac OS)上运行。 PyQt4 是双重许可的。 开发者可以在 GPL 和商业许可之间进行选择。 以前,GPL 版本仅在 Unix 上可用。 从 PyQt 版本 4 开始,GPL 许可证在所有受支持的平台上均可用。

PyQt4 的类分为几个模块:

  • QtCore
  • QtGui
  • QtNetwork
  • QtXml
  • QtSvg
  • QtOpenGL
  • QtSql

QtCore模块包含核心的非 GUI 功能。 该模块用于处理时间,文件和目录,各种数据类型,流,URL,mime 类型,线程或进程。 QtGui模块包含图形组件和相关类。 这些包括例如按钮,窗口,状态栏,工具栏,滑块,位图,颜色和字体。 QtNetwork模块包含用于网络编程的类。 这些类通过使网络编程更加容易和可移植性,来简化 TCP/IP 和 UDP 客户端和服务器的编码。 QtXml包含用于处理 XML 文件的类。 该模块提供了 SAX 和 DOM API 的实现。 QtSvg模块提供了用于显示 SVG 文件内容的类。 可伸缩矢量图形(SVG)是一种用于描述 XML 中的二维图形和图形应用的语言。 QtOpenGL模块用于使用 OpenGL 库渲染 3D 和 2D 图形。 该模块可实现 Qt GUI 库和 OpenGL 库的无缝集成。 QtSql模块提供了用于处理数据库的类。

Python

python logo Python 是一种通用的,动态的,面向对象的编程语言。 Python 语言的设计目的强调程序员的生产力和代码可读性。 Python 最初是由 Guido van Rossum 开发的。 它于 1991 年首次发布。Python 受 ABC,Haskell,Java,Lisp,Icon 和 Perl 编程语言的启发。 Python 是一种高级通用通用解释型语言。 Python 是一种简约语言。 它最明显的功能之一是它不使用分号或方括号。 它改用缩进。 目前,Python 有两个主要分支:Python 2.x 和 Python3.x。 Python 3.x 打破了与早期版本 Python 的向后兼容性。 它的创建是为了纠正该语言的某些设计缺陷并使该语言更简洁。 Python 由世界各地的一大批志愿者维护。 Python 是开源软件。 对于那些想学习编程的人来说,Python 是一个理想的起点。

本教程使用 Python 2.x 版本。

Python 编程语言支持多种编程样式。 它不会强迫程序员采用特定的示例。 Python 支持面向对象和过程编程。 对函数式编程的支持也很有限。

Python 编程语言的官方网站是 python.org

Perl,Python 和 Ruby 是广泛使用的脚本语言。 它们具有许多相似之处,并且是紧密的竞争对手。

Python 工具包

为了创建图形用户界面,Python 程序员可以在三个不错的选项中进行选择:PyQt4,PyGTK 和 wxPython。

本章是 PyQt4 工具包的介绍。

PyQt4 中的第一个程序

原文: http://zetcode.com/gui/pyqt4/firstprograms/

在 PyQt4 教程的这一部分中,我们将学习一些基本功能。

简单的例子

这是一个显示小窗口的简单示例。 然而,我们可以利用这个窗口做很多事情。 我们可以调整大小,最大化或最小化它。 这需要大量的编码。 已经有人对该功能进行了编码。 由于它在大多数应用中都会重复出现,因此无需重新编码。 PyQt4 是高级工具包。 如果我们使用较低级的工具箱进行编码,则下面的代码示例可能很容易包含数百行。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we create a simple
window in PyQt4.

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

def main():

    app = QtGui.QApplication(sys.argv)

    w = QtGui.QWidget()
    w.resize(250, 150)
    w.move(300, 300)
    w.setWindowTitle('Simple')
    w.show()

    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

上面的代码在屏幕上显示了一个小窗口。

import sys
from PyQt4 import QtGui

在这里,我们提供必要的导入。 基本的 GUI 小部件位于QtGui模块中。

app = QtGui.QApplication(sys.argv)

每个 PyQt4 应用都必须创建一个应用对象。 应用对象位于QtGui模块中。 sys.argv参数是命令行中的参数列表。 可以从外壳运行 Python 脚本。 这是我们可以控制脚本启动的方式。

w = QtGui.QWidget()

QtGui.QWidget小部件是 PyQt4 中所有用户界面对象的基类。 我们为QtGui.QWidget提供了默认的构造器。 默认构造器没有父代。 没有父级的窗口小部件称为窗口。

w.resize(250, 150)

resize()方法调整窗口小部件的大小。 宽 250 像素,高 150 像素。

w.move(300, 300)

move()方法将窗口小部件移动到屏幕上x = 300y = 300坐标的位置。

w.setWindowTitle('Simple')

在这里,我们为窗口设置标题。 标题显示在标题栏中。

w.show()

show()方法在屏幕上显示小部件。 首先在内存中创建一个小部件,然后将其显示在屏幕上。

sys.exit(app.exec_())

最后,我们进入应用的主循环。 事件处理从这一点开始。 主循环从窗口系统接收事件,并将其分配给应用小部件。 如果调用exit()方法或主窗口小部件被销毁,则主循环结束。 sys.exit()方法可确保干净退出。 将告知环境应用如何结束。

exec_()方法带有下划线。 这是因为exec是 Python 关键字。 因此,使用了exec_()

Simple

图:简单

应用图标

应用图标是一个小图像,通常显示在标题栏的左上角。 在下面的示例中,我们将展示如何在 PyQt4 中做到这一点。 我们还将介绍一些新方法。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This example shows an icon
in the titlebar of the window.

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Icon')
        self.setWindowIcon(QtGui.QIcon('web.png'))        

        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

前面的示例以过程样式编码。 Python 编程语言支持过程和面向对象的编程风格。 在 PyQt4 中进行编程意味着在 OOP 中进行编程。

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()
        ...

面向对象编程中最重要的三件事是类,数据和方法。 在这里,我们创建了一个名为Example的新类。 Example类继承自QtGui.QWidget类。 这意味着我们调用了两个构造器:第一个构造器用于Example类,第二个构造器用于继承的类。 super()方法返回Example类的父对象,我们将其称为构造器。 __init__()方法是 Python 中的构造方法。

self.initUI() 

GUI 的创建委托给initUI()方法。

self.setGeometry(300, 300, 250, 150)
self.setWindowTitle('Icon')
self.setWindowIcon(QtGui.QIcon('web.png'))  

所有这三种方法都从QtGui.QWidget类继承。 setGeometry()做两件事。 它在屏幕上找到窗口并设置其大小。 前两个参数是窗口的 x 和 y 位置。 第三个是窗口的宽度,第四个是窗口的高度。 实际上,它将resize()move()方法结合在一起。 最后一种方法设置应用图标。 为此,我们创建了一个QtGui.QIcon对象。 QtGui.QIcon接收到要显示的图标的路径。

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()   

启动代码已放置在main()方法中。 这是 Python 惯用语。

Icon

图:图标

显示工具提示

我们可以为我们的任何小部件提供气球帮助。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This example shows a tooltip on 
a window and a button

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))

        self.setToolTip('This is a <b>QWidget</b> widget')

        btn = QtGui.QPushButton('Button', self)
        btn.setToolTip('This is a <b>QPushButton</b> widget')
        btn.resize(btn.sizeHint())
        btn.move(50, 50)       

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Tooltips')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在此示例中,我们显示了两个 PyQt4 小部件的工具提示。

QtGui.QToolTip.setFont(QtGui.QFont('SansSerif', 10))

此静态方法设置用于呈现工具提示的字体。 我们使用 10pt SansSerif 字体。

self.setToolTip('This is a <b>QWidget</b> widget')

要创建工具提示,我们调用setTooltip()方法。 我们还可以使用 RTF 格式。

btn = QtGui.QPushButton('Button', self)
btn.setToolTip('This is a <b>QPushButton</b> widget')

我们创建一个按钮小部件并为其设置工具提示。

btn.resize(btn.sizeHint())
btn.move(50, 50)       

调整按钮的大小并在窗口上移动。 sizeHint()方法为按钮提供了建议的大小。

Tooltip

图:工具提示

关闭窗口

如何关闭窗口的明显方法是单击标题栏上的 x 标记。 在下一个示例中,我们将展示如何以编程方式关闭窗口。 我们将简要介绍信号和槽。

以下是我们将在示例中使用的QtGui.QPushButton的构造器。

QPushButton(string text, QWidget parent = None)

text参数是将在按钮上显示的文本。 parent是一个小部件,我们在其上放置了按钮。 在我们的情况下,它将是QtGui.QWidget

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program creates a quit
button. When we press the button,
the application terminates. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        qbtn = QtGui.QPushButton('Quit', self)
        qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)
        qbtn.resize(qbtn.sizeHint())
        qbtn.move(50, 50)       

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Quit button')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在此示例中,我们创建一个退出按钮。 单击按钮后,应用终止。

from PyQt4 import QtGui, QtCore

需要QtCore模块中的对象。 因此,我们导入模块。

qbtn = QtGui.QPushButton('Quit', self)

我们创建一个按钮。 该按钮是QtGui.QPushButton类的实例。 构造器的第一个参数是按钮的标签。 第二个参数是父窗口小部件。 父窗口小部件是Example小部件,通过继承它是QtGui.QWidget

qbtn.clicked.connect(QtCore.QCoreApplication.instance().quit)

PyQt4 中的事件处理系统是通过信号和槽机制构建的。 如果单击按钮,将发出信号clicked。 该槽可以是 Qt 槽或任何可调用的 Python。 QtCore.QCoreApplication包含主事件循环。 它处理并调度所有事件。 instance()方法为我们提供了其当前实例。 注意,QtCore.QCoreApplication是用QtGui.QApplication创建的。 单击的信号连接到quit()方法,该方法终止应用。 通信是在两个对象之间进行的:发送者和接收者。 发送者是按钮,接收者是应用对象。

Quit button

图:退出按钮

MessageDialog

默认情况下,如果单击标题栏上的 x 按钮,则QtGui.QWidget将关闭。 有时我们想要修改此默认行为。 例如,如果我们在编辑器中打开了一个文件,对此我们做了一些更改。 我们显示一个消息框以确认操作。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program shows a confirmation 
message box when we click on the close
button of the application window. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        self.setGeometry(300, 300, 250, 150)        
        self.setWindowTitle('Message box')    
        self.show()

    def closeEvent(self, event):

        reply = QtGui.QMessageBox.question(self, 'Message',
            "Are you sure to quit?", QtGui.QMessageBox.Yes | 
            QtGui.QMessageBox.No, QtGui.QMessageBox.No)

        if reply == QtGui.QMessageBox.Yes:
            event.accept()
        else:
            event.ignore()        

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

如果我们关闭QtGui.QWidget,则会生成QtGui.QCloseEvent。 要修改小部件的行为,我们需要重新实现closeEvent()事件处理器。

reply = QtGui.QMessageBox.question(self, 'Message',
    "Are you sure to quit?", QtGui.QMessageBox.Yes | 
    QtGui.QMessageBox.No, QtGui.QMessageBox.No)

我们显示一个带有两个按钮的消息框:是和否。第一个字符串出现在标题栏上。 第二个字符串是对话框显示的消息文本。 第三个参数指定出现在对话框中的按钮的组合。 最后一个参数是默认按钮。 该按钮最初具有键盘焦点。 返回值存储在reply变量中。

if reply == QtGui.QMessageBox.Yes:
    event.accept()
else:
    event.ignore()  

在这里,我们测试返回值。 如果单击“是”按钮,我们将接受导致小部件关闭和应用终止的事件。 否则,我们将忽略关闭事件。

Message box

图:消息框

屏幕上的居中窗口

以下脚本显示了如何在桌面屏幕上居中放置窗口。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program centers a window 
on the screen. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        self.resize(250, 150)
        self.center()

        self.setWindowTitle('Center')    
        self.show()

    def center(self):

        qr = self.frameGeometry()
        cp = QtGui.QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()     

QtGui.QDesktopWidget类提供有关用户桌面的信息,包括屏幕大小。

self.center()

将使窗口居中的代码位于自定义center()方法中。

qr = self.frameGeometry()

我们得到一个指定主窗口几何形状的矩形。 这包括任何窗框。

cp = QtGui.QDesktopWidget().availableGeometry().center()

我们计算出显示器的屏幕分辨率。 从这个分辨率,我们得到了中心点。

qr.moveCenter(cp)

我们的矩形已经具有宽度和高度。 现在,我们将矩形的中心设置为屏幕的中心。 矩形的大小不变。

self.move(qr.topLeft())

我们将应用窗口的左上角移动到qr矩形的左上角,从而将窗口居中放置在屏幕上。

在 PyQt4 教程的这一部分中,我们介绍了一些基础知识。

PyQt4 中的菜单和工具栏

原文: http://zetcode.com/gui/pyqt4/menusandtoolbars/

在 PyQt4 教程的这一部分中,我们将创建菜单和工具栏。 菜单是位于菜单栏中的一组命令。 工具栏上的按钮带有应用中的一些常用命令。

主窗口

QtGui.QMainWindow类提供一个主应用窗口。 这样可以创建带有状态栏,工具栏和菜单栏的经典应用框架。

状态栏

状态栏是用于显示状态信息的小部件。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program creates a statusbar.

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        self.statusBar().showMessage('Ready')

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Statusbar')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

状态栏是在QtGui.QMainWindow小部件的帮助下创建的。

self.statusBar().showMessage('Ready')

要获取状态栏,我们调用QtGui.QMainWindow类的statusBar()方法。 该方法的第一次调用将创建一个状态栏。 后续调用返回状态栏对象。 showMessage()在状态栏上显示一条消息。

菜单栏

菜单栏是 GUI 应用的常见部分。 它是位于各个菜单中的一组命令。 (Mac OS 对菜单栏的处理不同。要获得相似的结果,我们可以添加以下行:menubar.setNativeMenuBar(False)。)

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program creates a menubar. The
menubar has one menu with an exit action.

author: Jan Bodnar
website: zetcode.com 
last edited: August 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        exitAction = QtGui.QAction(QtGui.QIcon('exit.png'), '&Exit', self)        
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(QtGui.qApp.quit)

        self.statusBar()

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Menubar')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

在上面的示例中,我们创建一个带有一个菜单的菜单栏。 此菜单包含一个操作,如果选择该操作,则该应用将终止。 也会创建一个状态栏。 可通过 Ctrl + Q 快捷方式访问该操作。

exitAction = QtGui.QAction(QtGui.QIcon('exit.png'), '&Exit', self)        
exitAction.setShortcut('Ctrl+Q')
exitAction.setStatusTip('Exit application')

QtGui.QAction是使用菜单栏,工具栏或自定义键盘快捷键执行的操作的抽象。 在以上三行中,我们创建一个带有特定图标和“退出”标签的动作。 此外,为此操作定义了快捷方式。 第三行创建一个状态提示,当我们将鼠标指针悬停在菜单项上时,状态提示将显示在状态栏中。

exitAction.triggered.connect(QtGui.qApp.quit)

当我们选择此特定动作时,将触发信号。 信号连接到QtGui.QApplication小部件的quit()方法。 这将终止应用。

menubar = self.menuBar()
fileMenu = menubar.addMenu('&File')
fileMenu.addAction(exitAction)

menuBar()方法创建一个菜单栏。 我们创建一个文件菜单并将退出动作附加到该菜单。

工具栏

菜单将我们可以在应用中使用的所有命令分组。 使用工具栏可以快速访问最常用的命令。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program creates a toolbar.
The toolbar has one action, which
terminates the application if triggered.

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.triggered.connect(QtGui.qApp.quit)

        self.toolbar = self.addToolBar('Exit')
        self.toolbar.addAction(exitAction)

        self.setGeometry(300, 300, 300, 200)
        self.setWindowTitle('Toolbar')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在上面的示例中,我们创建了一个简单的工具栏。 工具栏有一个工具动作。 退出动作,在触发时终止应用。

exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(QtGui.qApp.quit)

与上面的菜单栏示例类似,我们创建一个动作对象。 该对象具有标签,图标和快捷方式。 QtGui.QMainWindowquit()方法连接到触发信号。

self.toolbar = self.addToolBar('Exit')
self.toolbar.addAction(exitAction)

在这里,我们创建了一个工具栏,并在其中插入了动作对象。

Toolbar

图:工具栏

把它放在一起

在本节的最后一个示例中,我们创建一个菜单栏,一个工具栏和一个状态栏。 我们还创建了一个中央小部件。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This program creates a skeleton of
a classic GUI application with a menubar,
toolbar, statusbar and a central widget. 

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):               

        textEdit = QtGui.QTextEdit()
        self.setCentralWidget(textEdit)

        exitAction = QtGui.QAction(QtGui.QIcon('exit24.png'), 'Exit', self)
        exitAction.setShortcut('Ctrl+Q')
        exitAction.setStatusTip('Exit application')
        exitAction.triggered.connect(self.close)

        self.statusBar()

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(exitAction)

        toolbar = self.addToolBar('Exit')
        toolbar.addAction(exitAction)

        self.setGeometry(300, 300, 350, 250)
        self.setWindowTitle('Main window')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

此代码示例使用菜单栏,工具栏和状态栏创建经典 GUI 应用的框架。

textEdit = QtGui.QTextEdit()
self.setCentralWidget(textEdit)

在这里,我们创建一个文本编辑小部件。 我们将其设置为QtGui.QMainWindow的中央小部件。 中央窗口小部件占据了所有剩余空间。

MainWindow

图:MainWindow

在 PyQt4 教程的这一部分中,我们使用了菜单,工具栏,状态栏和主应用窗口。

PyQt4 中的布局管理

原文: http://zetcode.com/gui/pyqt4/layoutmanagement/

GUI 编程中的一个重要方面是布局管理。 布局管理是我们将小部件放置在窗口上的方式。 管理可以通过两种基本方式完成。 我们可以使用绝对定位或布局类。

绝对定位

程序员以像素为单位指定每个小部件的位置和大小。 使用绝对定位时,我们必须了解以下限制:

  • 如果我们调整窗口大小,则小部件的大小和位置不会改变
  • 在各种平台上,应用看起来可能有所不同
  • 在我们的应用中更改字体可能会破坏布局
  • 如果我们决定更改布局,则必须完全重做布局,这既繁琐又耗时

以下示例将小部件放置在绝对坐标中。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This example shows three labels on a window
using absolute positioning. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        lbl1 = QtGui.QLabel('ZetCode', self)
        lbl1.move(15, 10)

        lbl2 = QtGui.QLabel('tutorials', self)
        lbl2.move(35, 40)

        lbl3 = QtGui.QLabel('for programmers', self)
        lbl3.move(55, 70)        

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Absolute')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

我们使用move()方法定位小部件。 在我们的例子中,这些是标签。 我们通过提供 x 和 y 坐标来定位它们。 坐标系的起点在左上角。 x 值从左到右增长。 y 值从上到下增长。

lbl1 = QtGui.QLabel('Zetcode', self)
lbl1.move(15, 10)

标签窗口小部件位于x=15y=10处。

Absolute positioning

图:绝对定位

盒子布局

具有布局类的布局管理更加灵活和实用。 这是在窗口上放置小部件的首选方法。 QtGui.QHBoxLayoutQtGui.QVBoxLayout是基本的布局类,可水平和垂直排列小部件。

想象一下,我们想在右下角放置两个按钮。 为了创建这样的布局,我们将使用一个水平框和一个垂直框。 为了创建必要的空间,我们将添加拉伸因子。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we position two push
buttons in the bottom-right corner 
of the window. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        okButton = QtGui.QPushButton("OK")
        cancelButton = QtGui.QPushButton("Cancel")

        hbox = QtGui.QHBoxLayout()
        hbox.addStretch(1)
        hbox.addWidget(okButton)
        hbox.addWidget(cancelButton)

        vbox = QtGui.QVBoxLayout()
        vbox.addStretch(1)
        vbox.addLayout(hbox)

        self.setLayout(vbox)    

        self.setGeometry(300, 300, 300, 150)
        self.setWindowTitle('Buttons')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

该示例在窗口的右下角放置了两个按钮。 当我们调整应用窗口的大小时,它们会停留在该位置。 我们同时使用QtGui.HBoxLayoutQtGui.QVBoxLayout

okButton = QtGui.QPushButton("OK")
cancelButton = QtGui.QPushButton("Cancel")

在这里,我们创建两个按钮。

hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(okButton)
hbox.addWidget(cancelButton)

我们创建一个水平框布局,并添加一个拉伸因子和两个按钮。 拉伸在两个按钮之前增加了可拉伸的空间。 这会将它们推到窗口的右侧。

vbox = QtGui.QVBoxLayout()
vbox.addStretch(1)
vbox.addLayout(hbox)

为了创建必要的布局,我们将水平布局放入垂直布局。 垂直框中的拉伸因子会将带有按钮的水平框推到窗口底部。

self.setLayout(vbox)

最后,我们设置窗口的主要布局。

Buttons

图:按钮

QtGui.QGridLayout

最通用的布局类是网格布局。 此布局将空间分为行和列。 要创建网格布局,我们使用QtGui.QGridLayout类。

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys
from PyQt4 import QtGui

"""
ZetCode PyQt4 tutorial 

In this example, we create a skeleton
of a calculator using a QtGui.QGridLayout.

author: Jan Bodnar
website: zetcode.com 
last edited: July 2014
"""

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        grid = QtGui.QGridLayout()
        self.setLayout(grid)

        names = ['Cls', 'Bck', '', 'Close',
                 '7', '8', '9', '/',
                '4', '5', '6', '*',
                 '1', '2', '3', '-',
                '0', '.', '=', '+']

        positions = [(i,j) for i in range(5) for j in range(4)]

        for position, name in zip(positions, names):

            if name == '':
                continue
            button = QtGui.QPushButton(name)
            grid.addWidget(button, *position)

        self.move(300, 150)
        self.setWindowTitle('Calculator')
        self.show()

def main():
    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在我们的示例中,我们创建了一个按钮网格。

grid = QtGui.QGridLayout()
self.setLayout(grid)

创建QtGui.QGridLayout的实例,并将其设置为应用窗口的布局。

names = ['Cls', 'Bck', '', 'Close',
            '7', '8', '9', '/',
        '4', '5', '6', '*',
            '1', '2', '3', '-',
        '0', '.', '=', '+']

这些是稍后用于按钮的标签。

positions = [(i,j) for i in range(5) for j in range(4)]

我们在网格中创建位置列表。

for position, name in zip(positions, names):

    if name == '':
        continue
    button = QtGui.QPushButton(name)
    grid.addWidget(button, *position)

使用addWidget()方法创建按钮并将其添加到布局。

Calculator skeleton

图:计算机骨架

回顾示例

小部件可以跨越网格中的多个列或行。 在下一个示例中,我们将对此进行说明。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we create a bit
more complicated window layout using
the QtGui.QGridLayout manager. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        title = QtGui.QLabel('Title')
        author = QtGui.QLabel('Author')
        review = QtGui.QLabel('Review')

        titleEdit = QtGui.QLineEdit()
        authorEdit = QtGui.QLineEdit()
        reviewEdit = QtGui.QTextEdit()

        grid = QtGui.QGridLayout()
        grid.setSpacing(10)

        grid.addWidget(title, 1, 0)
        grid.addWidget(titleEdit, 1, 1)

        grid.addWidget(author, 2, 0)
        grid.addWidget(authorEdit, 2, 1)

        grid.addWidget(review, 3, 0)
        grid.addWidget(reviewEdit, 3, 1, 5, 1)

        self.setLayout(grid) 

        self.setGeometry(300, 300, 350, 300)
        self.setWindowTitle('Review')    
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

我们创建一个窗口,其中有三个标签,两个行编辑和一个文本编辑小部件。 使用QtGui.QGridLayout完成布局。

grid = QtGui.QGridLayout()
grid.setSpacing(10)

我们创建网格布局并设置小部件之间的间距。

grid.addWidget(reviewEdit, 3, 1, 5, 1)

如果我们将小部件添加到网格,则可以提供小部件的行跨度和列跨度。 在我们的例子中,我们使reviewEdit小部件跨越 5 行。

Review example

图:回顾 example

PyQt4 教程的这一部分专门用于布局管理。

Windows API 控件 II

原文: http://zetcode.com/gui/winapi/controlsII/

我们继续使用 Windows 控件。 我们将展示如何使用跟踪栏,工具提示和月历控件。

跟踪栏

跟踪栏是一个包含滑块和可选刻度线的窗口。 我们使用鼠标或键盘移动滑块。 跟踪栏用于从一系列连续值中选择离散值。 在其他平台上,此控件称为滑块。

trackbar.c

#include <windows.h>
#include <commctrl.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateControls(HWND hwnd);
void UpdateLabel(void);

HWND hTrack;
HWND hlbl;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
  PWSTR lpCmdLine, int nCmdShow) {

    HWND hwnd;
    MSG  msg ;

    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Trackbar";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0,IDC_ARROW);

    RegisterClassW(&wc);
    hwnd = CreateWindowW(wc.lpszClassName, L"Trackbar", 
        WS_OVERLAPPEDWINDOW | WS_VISIBLE, 100, 100, 350, 180, 0, 0, hInstance, 0);

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
  WPARAM wParam, LPARAM lParam) {

  switch(msg) {

     case WM_CREATE:
       CreateControls(hwnd);
       break;

     case WM_HSCROLL:
       UpdateLabel();
       break;

     case WM_DESTROY:
       PostQuitMessage(0);
       break; 
  }

  return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CreateControls(HWND hwnd) {

    HWND hLeftLabel = CreateWindowW(L"Static", L"0", 
        WS_CHILD | WS_VISIBLE, 0, 0, 10, 30, hwnd, (HMENU)1, NULL, NULL);

    HWND hRightLabel = CreateWindowW(L"Static", L"100", 
        WS_CHILD | WS_VISIBLE, 0, 0, 30, 30, hwnd, (HMENU)2, NULL, NULL);

    hlbl = CreateWindowW(L"Static", L"0", WS_CHILD | WS_VISIBLE, 
        270, 20, 30, 30, hwnd, (HMENU)3, NULL, NULL);

    INITCOMMONCONTROLSEX icex;

    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC  = ICC_LISTVIEW_CLASSES;
    InitCommonControlsEx(&icex); 

    hTrack = CreateWindowW(TRACKBAR_CLASSW, L"Trackbar Control",
        WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS,
        20, 20, 170, 30, hwnd, (HMENU) 3, NULL, NULL);

    SendMessageW(hTrack, TBM_SETRANGE,  TRUE, MAKELONG(0, 100)); 
    SendMessageW(hTrack, TBM_SETPAGESIZE, 0,  10); 
    SendMessageW(hTrack, TBM_SETTICFREQ, 10, 0); 
    SendMessageW(hTrack, TBM_SETPOS, FALSE, 0); 
    SendMessageW(hTrack, TBM_SETBUDDY, TRUE, (LPARAM) hLeftLabel); 
    SendMessageW(hTrack, TBM_SETBUDDY, FALSE, (LPARAM) hRightLabel); 
}

void UpdateLabel(void) {

    LRESULT pos = SendMessageW(hTrack, TBM_GETPOS, 0, 0);
    wchar_t buf[4];
    wsprintfW(buf, L"%ld", pos);

    SetWindowTextW(hlbl, buf);
}

在我们的示例中,我们显示带有三个静态文本控件的跟踪栏控件。 其中两个连接在跟踪栏的左侧和右侧。 他们被称为伙伴。 通过拖动滑块,我们可以更改第三个静态控件的文本。

HWND hLeftLabel = CreateWindowW(L"Static", L"0", 
    WS_CHILD | WS_VISIBLE, 0, 0, 10, 30, hwnd, (HMENU)1, NULL, NULL);

HWND hRightLabel = CreateWindowW(L"Static", L"100", 
    WS_CHILD | WS_VISIBLE, 0, 0, 30, 30, hwnd, (HMENU)2, NULL, NULL);

hlbl = CreateWindowW(L"Static", L"0", WS_CHILD | WS_VISIBLE, 
    270, 20, 30, 30, hwnd, (HMENU)3, NULL, NULL);

创建了三个静态控件。 两个控件将显示跟踪栏控件的最小值和最大值。 最后一个将显示当前选定的值。

INITCOMMONCONTROLSEX icex;

icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC  = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex); 

如果要使用公共控件之一,则需要加载公共控件 DLL(comctl32.dll)并从 DLL 中注册特定的公共控件类。 在创建通用控件之前,InitCommonControlsEx()必须调用此函数。

hTrack = CreateWindowW(TRACKBAR_CLASSW, L"Trackbar Control",
    WS_CHILD | WS_VISIBLE | TBS_AUTOTICKS,
    20, 20, 170, 30, hwnd, (HMENU) 3, NULL, NULL);

TRACKBAR_CLASSW用于创建轨迹栏控件。 TBS_AUTOTICKS样式会为其值范围内的每个增量创建一个刻度线。

SendMessageW(hTrack, TBM_SETRANGE,  TRUE, MAKELONG(0, 100)); 
SendMessageW(hTrack, TBM_SETPAGESIZE, 0,  10); 
SendMessageW(hTrack, TBM_SETTICFREQ, 10, 0); 
SendMessageW(hTrack, TBM_SETPOS, FALSE, 0); 

跟踪栏控件尚未完成。 我们向控件发送四个消息。 我们发送TBM_SETRANGE设置轨迹栏范围。 要设置页面大小,我们发送TBM_SETPAGESIZE消息。 要设置刻度频率,我们发送TBM_SETTICFREQ消息。 要设置当前滑块位置,我们发送TBM_SETPOS

SendMessageW(hTrack, TBM_SETBUDDY, TRUE, (LPARAM) hLeftLabel); 
SendMessageW(hTrack, TBM_SETBUDDY, FALSE, (LPARAM) hRightLabel);  

我们通过发送TBM_SETBUDDY消息来设置轨迹栏好友。 第三个参数将决定伙伴是位于控件的左侧(TRUE)还是右侧(FALSE)。

case WM_HSCROLL:
  UpdateLabel();
  break;

当我们移动轨迹栏滑块时,窗口过程将收到WM_HSCROLL消息。 (如果是水平跟踪栏。)

void UpdateLabel(void) {

    LRESULT pos = SendMessageW(hTrack, TBM_GETPOS, 0, 0);
    wchar_t buf[4];
    wsprintfW(buf, L"%ld", pos);

    SetWindowTextW(hlbl, buf);
}

UpdateLabel()函数中,我们通过发送TMB_GETPOS消息来获得当前滑块的位置。 使用wsprintfW()函数将接收到的值转换为文本。 最后,通过SetWindowTextW()函数设置静态控件的文本。

Trackbar

图:跟踪栏

工具提示

工具提示是常见的图形用户元素。 工具提示大部分时间都是隐藏的。 这是一个小框,当鼠标指针经过时会出现在 GUI 对象附近。 它显示一条简短的消息,说明该对象。 工具提示是应用帮助系统的一部分。

tooltip.c

#include <windows.h>
#include <commctrl.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateMyTooltip(HWND);

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
            LPSTR lpCmdLine, int nCmdShow) {
    MSG  msg;    
    WNDCLASS wc = {0};
    wc.lpszClassName = "Tooltip";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClass(&wc);
    CreateWindow(wc.lpszClassName, "Tooltip",
                WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                100, 100, 200, 150, 0, 0, hInstance, 0);  

    while (GetMessage(&msg, NULL, 0, 0)) {

        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {

    switch(msg) {

      case WM_CREATE:
          CreateMyTooltip(hwnd);
          break;

      case WM_DESTROY:
          PostQuitMessage(0);
          break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

void CreateMyTooltip(HWND hwnd) {

    INITCOMMONCONTROLSEX iccex; 
    HWND hwndTT;                

    TOOLINFO ti;
    char tooltip[30] = "A main window";
    RECT rect;                 

    iccex.dwICC = ICC_WIN95_CLASSES;
    iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    InitCommonControlsEx(&iccex);

    hwndTT = CreateWindowEx(WS_EX_TOPMOST, TOOLTIPS_CLASS, NULL,
        WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,		
        0, 0, 0, 0, hwnd, NULL, NULL, NULL );

    SetWindowPos(hwndTT, HWND_TOPMOST, 0, 0, 0, 0,
        SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

    GetClientRect(hwnd, &rect);

    ti.cbSize = sizeof(TOOLINFO);
    ti.uFlags = TTF_SUBCLASS;
    ti.hwnd = hwnd;
    ti.hinst = NULL;
    ti.uId = 0;
    ti.lpszText = tooltip;
    ti.rect.left = rect.left;    
    ti.rect.top = rect.top;
    ti.rect.right = rect.right;
    ti.rect.bottom = rect.bottom;

    SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);	
} 		

在我们的示例中,我们为主窗口设置了一个工具提示。

INITCOMMONCONTROLSEX iccex;
...
iccex.dwICC = ICC_WIN95_CLASSES;
iccex.dwSize = sizeof(INITCOMMONCONTROLSEX);
InitCommonControlsEx(&iccex);

工具提示是通用控件的一部分,因此,我们必须初始化通用控件。

工具提示的创建包含几个步骤。 我们必须创建一个工具提示窗口。 然后我们将其设置为最顶层的窗口,这样它就不会被另一个窗口覆盖。 我们创建一个工具提示文本和TOOLTIPINFO结构。 该结构必须填充重要信息。 窗口句柄,工具提示文本和矩形将覆盖我们的工具提示。 在我们的示例中,我们的工具提示将覆盖窗口的整个客户区域。

SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) &ti);

在发送TTM_ADDTOOL消息后,工具提示确实添加到了窗口中。

Tooltip control

图:工具提示 control

Updown 控件

Updown 控件(也称为旋转控件)将一对显示为箭头的按钮与一个好友编辑控件结合在一起。 单击箭头可增加或减少编辑控件中的值。 Updown 控件是使用UPDOWN_CLASSW窗口类创建的。

updown.c

#include <windows.h>
#include <commctrl.h>
#include <strsafe.h>

#define ID_UPDOWN 1
#define ID_EDIT 2
#define ID_STATIC 3
#define UD_MAX_POS 30
#define UD_MIN_POS 0

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateControls(HWND);

HWND hUpDown, hEdit, hStatic;

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    PWSTR lpCmdLine, int nCmdShow) {

    MSG  msg;
    WNDCLASSW wc = {0};

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpszClassName = L"Updown control";
    wc.hInstance     = hInstance;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);
    CreateWindowW(wc.lpszClassName, L"Updown control",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        100, 100, 280, 200, NULL, NULL, hInstance, NULL);

    while (GetMessage(&msg, NULL, 0, 0)) {

        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    LPNMUPDOWN lpnmud;
    UINT code;

    switch(msg) {

        case WM_CREATE:

            CreateControls(hwnd);

            break;

        case WM_NOTIFY:

            code = ((LPNMHDR) lParam)->code;

            if (code == UDN_DELTAPOS) {

                lpnmud = (NMUPDOWN *) lParam;                

                int value = lpnmud->iPos + lpnmud->iDelta;

                if (value < UD_MIN_POS) {
                    value = UD_MIN_POS;
                }

                if (value > UD_MAX_POS) {
                    value = UD_MAX_POS;
                }

                const int asize = 4;
                wchar_t buf[asize];
                size_t cbDest = asize * sizeof(wchar_t);
                StringCbPrintfW(buf, cbDest, L"%d", value);

                SetWindowTextW(hStatic, buf);                  
            }

            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CreateControls(HWND hwnd) {

    INITCOMMONCONTROLSEX icex;

    icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC  = ICC_UPDOWN_CLASS;
    InitCommonControlsEx(&icex); 

    hUpDown = CreateWindowW(UPDOWN_CLASSW, NULL, WS_CHILD | WS_VISIBLE 
        | UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 
        0, 0, 0, 0, hwnd, (HMENU) ID_UPDOWN, NULL, NULL);

    hEdit = CreateWindowExW(WS_EX_CLIENTEDGE, WC_EDITW, NULL, WS_CHILD 
        | WS_VISIBLE | ES_RIGHT, 15, 15, 70, 25, hwnd, 
        (HMENU) ID_EDIT, NULL, NULL);

    hStatic = CreateWindowW(WC_STATICW, L"0", WS_CHILD | WS_VISIBLE
        | SS_LEFT, 15, 60, 300, 230, hwnd, (HMENU) ID_STATIC, NULL, NULL);

    SendMessageW(hUpDown, UDM_SETBUDDY, (WPARAM) hEdit, 0);
    SendMessageW(hUpDown, UDM_SETRANGE, 0, MAKELPARAM(UD_MAX_POS, UD_MIN_POS));
    SendMessageW(hUpDown, UDM_SETPOS32, 0, 0);
}

在代码示例中,我们有一个 UpDown 控件和一个静态文本控件。 当前选定的 UpDown 值显示在静态文本控件中。

#define UD_MAX_POS 30
#define UD_MIN_POS 0

这两个常数用于 UpDown 控件的最大值和最小值。

hUpDown = CreateWindowW(UPDOWN_CLASSW, NULL, WS_CHILD | WS_VISIBLE 
    | UDS_SETBUDDYINT | UDS_ALIGNRIGHT, 
    0, 0, 0, 0, hwnd, (HMENU) ID_UPDOWN, g_hInst, NULL);

为了创建 UpDown 控件,我们将UPDOWN_CLASSW传递给CreateWindowW()函数。 UDS_SETBUDDYINT标志使 UpDown 控件在其位置更改时向其好友发送消息(WM_SETTEXT)。 UDS_ALIGNRIGHT标志将 UpDown 控件放置在其伙伴窗口右边缘的旁边。

SendMessageW(hUpDown, UDM_SETBUDDY, (WPARAM) hEdit, 0);

UDM_SETBUDDY消息将编辑控件设置为 UpDown 控件的好友窗口。

SendMessageW(hUpDown, UDM_SETRANGE, 0, MAKELPARAM(UD_MAX_POS, UD_MIN_POS));

UDM_SETRANGE消息设置 UpDown 控件的最小和最大位置。

SendMessageW(hUpDown, UDM_SETPOS32, 0, 0);

通过UDM_SETPOS32消息,我们设置 UpDown 控件的初始位置。

code = ((LPNMHDR) lParam)->code;

if (code == UDN_DELTAPOS) {
...
}

当控件的位置即将更改时(即在控件更新其值之前),操作系统会将UDN_DELTAPOS通知发送到 UpDown 控件的父窗口。

lpnmud = (NMUPDOWN *) lParam;

int value = lpnmud->iPos + lpnmud->iDelta;

NMUPDOWN结构包含有关 UpDown 修改的信息。 iPos值是 UpDown 控件的当前位置。 iDelta是 UpDown 控件位置的建议更改。 根据这两个值,我们计算出将出现在控件中的最终值。

if (value < UD_MIN_POS) {
    value = UD_MIN_POS;
}

if (value > UD_MAX_POS) {
    value = UD_MAX_POS;
}

此代码确保静态文本不会显示超出 UpDown 范围的值。

int const asize = 4;
wchar_t buf[asize];
size_t cbDest = asize * sizeof(wchar_t);
StringCbPrintfW(buf, cbDest, L"%d", value);

使用StringCbPrintfW()函数,我们构建了要在静态文本控件中显示的字符串。

SetWindowTextW(hStatic, buf);

最后,使用SetWindowTextW()函数更新静态文本控件。

UpDown control

图:UpDown 控件

MonthCalendar控件

月历是一个复杂的控件,用于选择日期。 可以通过简单直观的方式选择日期。

monthcalendar.c

#include <windows.h>
#include <commctrl.h>
#include <wchar.h>
#include <strsafe.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void CreateControls(HWND);
void GetSelectedDate(HWND, HWND);

HWND hStat;
HWND hMonthCal;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
  LPSTR lpCmdLine, int nCmdShow) {

    HWND hwnd;
    MSG  msg;

    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Month Calendar";
    wc.hInstance     = hInstance ;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc ;
    wc.hCursor       = LoadCursor(0, IDC_ARROW);

    RegisterClassW(&wc);

    hwnd = CreateWindowW(wc.lpszClassName, L"Month Calendar",
        WS_OVERLAPPEDWINDOW | WS_VISIBLE,
        100, 100, 250, 300, 0, 0, hInstance, 0);  

    while (GetMessage(&msg, NULL, 0, 0)) {

        DispatchMessage(&msg);
    }

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
    WPARAM wParam, LPARAM lParam) {

    LPNMHDR lpNmHdr;

    switch(msg) {

        case WM_CREATE:

            CreateControls(hwnd);
            break;

        case WM_NOTIFY:

            lpNmHdr = (LPNMHDR) lParam;

            if (lpNmHdr->code == MCN_SELECT) {
                GetSelectedDate(hMonthCal, hStat);
            }

            break;

        case WM_DESTROY:

            PostQuitMessage(0);
            break; 
    }

    return DefWindowProcW(hwnd, msg, wParam, lParam);
}

void CreateControls(HWND hwnd) {

    hStat = CreateWindowW(WC_STATICW, L"", 
        WS_CHILD | WS_VISIBLE, 80, 240, 80, 30,
        hwnd, (HMENU)1, NULL, NULL);

    INITCOMMONCONTROLSEX icex;

    icex.dwSize = sizeof(icex);
    icex.dwICC  = ICC_DATE_CLASSES;
    InitCommonControlsEx(&icex);

    hMonthCal = CreateWindowW(MONTHCAL_CLASSW, L"", 
        WS_BORDER | WS_CHILD | WS_VISIBLE | MCS_NOTODAYCIRCLE,  
        20, 20, 200, 200, hwnd, (HMENU)2, NULL, NULL);
}

void GetSelectedDate(HWND hMonthCal, HWND hStat) {

    SYSTEMTIME time;
    const int dsize = 20;
    wchar_t buf[dsize];

    ZeroMemory(&time, sizeof(SYSTEMTIME));
    SendMessage(hMonthCal, MCM_GETCURSEL, 0, (LPARAM) &time);

    size_t cbDest = dsize * sizeof(wchar_t);
    StringCbPrintfW(buf, cbDest, L"%d-%d-%d", 
          time.wYear, time.wMonth, time.wDay);

    SetWindowTextW(hStat, buf);
}

在我们的示例中,我们有两个控件:月历控件和静态文本。 从月历控件中选择的日期以静态文本显示。

hMonthCal = CreateWindowW(MONTHCAL_CLASSW, L"", 
    WS_BORDER | WS_CHILD | WS_VISIBLE | MCS_NOTODAYCIRCLE,  
    20, 20, 200, 200, hwnd, (HMENU)2, NULL, NULL);

在这里,我们创建一个月历控件。 创建月历控件的类名称为MONTHCAL_CLASSW。 如果使用MCS_NOTODAYCIRCLE窗口样式,则不会圈出今天的日期。

INITCOMMONCONTROLSEX icex;

icex.dwSize = sizeof(icex);
icex.dwICC  = ICC_DATE_CLASSES;
InitCommonControlsEx(&icex);

要注册月历控件,我们为INITCOMMONCONTROLSEX结构的dwICC成员指定ICC_DATE_CLASSES标志。

case WM_NOTIFY:

    lpNmHdr = (LPNMHDR) lParam;

    if (lpNmHdr->code == MCN_SELECT) {
        GetSelectedDate(hMonthCal, hStat);
    }

    break;

如果月份日历控件中发生事件,则会发送WM_NOTIFY消息。 lParam包含指向NMHDR结构的指针,该结构包含通知代码和其他信息。

SendMessage(hMonthCal, MCM_GETCURSEL, 0, (LPARAM) &time);

为了用选定的日期填充结构,我们向日历控件发送了MCM_GETCURSEL消息。

size_t cbDest = dsize * sizeof(wchar_t);
StringCbPrintfW(buf, cbDest, L"%d-%d-%d", 
      time.wYear, time.wMonth, time.wDay);

SetWindowTextW(hStat, buf);

我们构建字符串并将日期设置为静态文本控件。

Month Calendar

图:月历

在 Windows API 教程的这一部分中,我们继续介绍 Windows 控件-跟踪栏,工具提示,上下按钮和月份日历。

PyQt4 中的事件和信号

原文: http://zetcode.com/gui/pyqt4/eventsandsignals/

在 PyQt4 编程教程的这一部分中,我们将探讨应用中发生的事件和信号。

事件

所有 GUI 应用都是事件驱动的。 事件主要由应用的用户生成。 但是它们也可以通过其他方式生成:例如互联网连接,窗口管理器或计时器。 当我们调用应用的exec_()方法时,应用进入主循环。 主循环获取事件并将其发送到对象。

在事件模型中,有三个参与者:

  • 事件来源
  • 事件对象
  • 事件目标

事件源是状态更改的对象。 它生成事件。 事件对象(事件)将状态更改封装在事件源中。 事件目标是要通知的对象。 事件源对象将处理事件的任务委托给事件目标。

PyQt4 具有独特的信号和槽机制来处理事件。 信号和槽用于对象之间的通信。 当发生特定事件时,会发出信号。 槽可以是任何 Python 可调用的。 当发出与其连接的信号时,将调用槽。

新 API

PyQt4.5 引入了一种用于处理信号和槽的新型 API。

QtCore.QObject.connect(button, QtCore.SIGNAL('clicked()'), self.onClicked)

这是旧式的 API。

button.clicked.connect(self.onClicked)

新样式更加符合 Python 标准。

信号和槽

这是一个简单的示例,展示了 PyQt4 中的信号和槽。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we connect a signal
of a QtGui.QSlider to a slot 
of a QtGui.QLCDNumber. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):

        lcd = QtGui.QLCDNumber(self)
        sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)

        vbox = QtGui.QVBoxLayout()
        vbox.addWidget(lcd)
        vbox.addWidget(sld)

        self.setLayout(vbox)
        sld.valueChanged.connect(lcd.display)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Signal & slot')
        self.show()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在我们的示例中,我们显示QtGui.QLCDNumberQtGui.QSlider。 我们通过拖动滑块来更改lcd编号。

sld.valueChanged.connect(lcd.display)

在这里,我们将滑块的valueChanged信号连接到lcd号的display槽。

发送器是发送信号的对象。 接收器是接收信号的对象。 槽是对信号做出反应的方法。

Signals & slot

图:信号和槽

重新实现事件处理器

PyQt4 中的事件通常通过重新实现事件处理器来处理。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we reimplement an 
event handler. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('Event handler')
        self.show()

    def keyPressEvent(self, e):

        if e.key() == QtCore.Qt.Key_Escape:
            self.close()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在我们的示例中,我们重新实现了keyPressEvent()事件处理器。

def keyPressEvent(self, e):

    if e.key() == QtCore.Qt.Key_Escape:
        self.close()

如果单击“退出”按钮,则应用终止。

事件发送者

有时很方便地知道哪个窗口小部件是信号的发送者。 为此,PyQt4 具有sender()方法。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we determine the event sender
object.

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        btn1 = QtGui.QPushButton("Button 1", self)
        btn1.move(30, 50)

        btn2 = QtGui.QPushButton("Button 2", self)
        btn2.move(150, 50)

        btn1.clicked.connect(self.buttonClicked)            
        btn2.clicked.connect(self.buttonClicked)

        self.statusBar()

        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Event sender')
        self.show()

    def buttonClicked(self):

        sender = self.sender()
        self.statusBar().showMessage(sender.text() + ' was pressed')

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

我们的示例中有两个按钮。 在buttonClicked()方法中,我们通过调用sender()方法来确定单击了哪个按钮。

btn1.clicked.connect(self.buttonClicked)            
btn2.clicked.connect(self.buttonClicked)

两个按钮都连接到同一槽。

def buttonClicked(self):

    sender = self.sender()
    self.statusBar().showMessage(sender.text() + ' was pressed')

我们通过调用sender()方法来确定信号源。 在应用的状态栏中,我们显示了被按下的按钮的标签。

Event sender

图:事件发送者

发射信号

QtCore.QObject创建的对象可以发出信号。 在下面的示例中,我们将看到如何发出自定义信号。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we show how to emit a
signal. 

author: Jan Bodnar
website: zetcode.com 
last edited: January 2015
"""

import sys
from PyQt4 import QtGui, QtCore

class Communicate(QtCore.QObject):

    closeApp = QtCore.pyqtSignal() 

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        self.c = Communicate()
        self.c.closeApp.connect(self.close)       

        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Emit signal')
        self.show()

    def mousePressEvent(self, event):

        self.c.closeApp.emit()

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

我们创建一个名为closeApp的新信号。 在鼠标按下事件期间发出此信号。 信号连接到QtGui.QMainWindowclose()槽。

class Communicate(QtCore.QObject):

    closeApp = QtCore.pyqtSignal()     

使用QtCore.pyqtSignal()作为外部Communicate类的类属性创建信号。

self.c.closeApp.connect(self.close) 

定制的closeApp信号连接到QtGui.QMainWindowclose()槽。

def mousePressEvent(self, event):

    self.c.closeApp.emit()

当我们用鼠标指针单击窗口时,会发出closeApp信号。 该应用终止。

在 PyQt4 教程的这一部分中,我们介绍了信号和槽。

PyQt4 中的对话框

原文: http://zetcode.com/gui/pyqt4/dialogs/

对话框窗口或对话框是大多数现代 GUI 应用必不可少的部分。 对话被定义为两个或更多人之间的对话。 在计算机应用中,对话框是一个窗口,用于与应用“对话”。 对话框用于输入数据,修改数据,更改应用设置等。

QtGui.QInputDialog

QtGui.QInputDialog提供了一个简单的便捷对话框,可从用户那里获取单个值。 输入值可以是字符串,数字或列表中的项目。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we receive data from
a QtGui.QInputDialog dialog. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        self.btn = QtGui.QPushButton('Dialog', self)
        self.btn.move(20, 20)
        self.btn.clicked.connect(self.showDialog)

        self.le = QtGui.QLineEdit(self)
        self.le.move(130, 22)

        self.setGeometry(300, 300, 290, 150)
        self.setWindowTitle('Input dialog')
        self.show()

    def showDialog(self):

        text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 
            'Enter your name:')

        if ok:
            self.le.setText(str(text))

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

该示例具有一个按钮和一个行编辑小部件。 该按钮显示用于获取文本值的输入对话框。 输入的文本将显示在行编辑小部件中。

text, ok = QtGui.QInputDialog.getText(self, 'Input Dialog', 
    'Enter your name:')

这行显示输入对话框。 第一个字符串是对话框标题,第二个字符串是对话框中的消息。 对话框返回输入的文本和布尔值。 如果单击“确定”按钮,则布尔值为true

if ok:
    self.le.setText(str(text))

我们从对话框中收到的文本设置为行编辑小部件。

Input Dialog

图:输入对话框

QtGui.QColorDialog

QtGui.QColorDialog提供了一个对话框小部件,用于选择颜色值。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we select a colour value
from the QtGui.QColorDialog and change the background
colour of a QtGui.QFrame widget. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        col = QtGui.QColor(0, 0, 0) 

        self.btn = QtGui.QPushButton('Dialog', self)
        self.btn.move(20, 20)

        self.btn.clicked.connect(self.showDialog)

        self.frm = QtGui.QFrame(self)
        self.frm.setStyleSheet("QWidget { background-color: %s }" 
            % col.name())
        self.frm.setGeometry(130, 22, 100, 100)            

        self.setGeometry(300, 300, 250, 180)
        self.setWindowTitle('Color dialog')
        self.show()

    def showDialog(self):

        col = QtGui.QColorDialog.getColor()

        if col.isValid():
            self.frm.setStyleSheet("QWidget { background-color: %s }"
                % col.name())

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

该应用示例显示了一个按钮和一个QtGui.QFrame。 窗口小部件背景设置为黑色。 使用QtGui.QColorDialog,我们可以更改其背景。

col = QtGui.QColor(0, 0, 0) 

这是QtGui.QFrame背景的初始颜色。

col = QtGui.QColorDialog.getColor()

这行会弹出QtGui.QColorDialog

if col.isValid():
    self.frm.setStyleSheet("QWidget { background-color: %s }"
        % col.name())

我们检查颜色是否有效。 如果单击“取消”按钮,则不会返回有效的颜色。 如果颜色有效,我们将使用样式表更改背景颜色。

Color dialog

图:颜色对话框

QtGui.QFontDialog

QtGui.QFontDialog是用于选择字体的对话框小部件。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we select a font name
and change the font of a label. 

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        vbox = QtGui.QVBoxLayout()

        btn = QtGui.QPushButton('Dialog', self)
        btn.setSizePolicy(QtGui.QSizePolicy.Fixed,
            QtGui.QSizePolicy.Fixed)

        btn.move(20, 20)

        vbox.addWidget(btn)

        btn.clicked.connect(self.showDialog)

        self.lbl = QtGui.QLabel('Knowledge only matters', self)
        self.lbl.move(130, 20)

        vbox.addWidget(self.lbl)
        self.setLayout(vbox)          

        self.setGeometry(300, 300, 250, 180)
        self.setWindowTitle('Font dialog')
        self.show()

    def showDialog(self):

        font, ok = QtGui.QFontDialog.getFont()
        if ok:
            self.lbl.setFont(font)

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在我们的示例中,我们有一个按钮和一个标签。 使用QtGui.QFontDialog,我们更改标签的字体。

font, ok = QtGui.QFontDialog.getFont()

在这里我们弹出字体对话框。 getFont()方法返回字体名称和ok参数。 如果用户单击“确定”,则等于True。 否则为False

if ok:
    self.label.setFont(font)

如果单击确定,则标签的字体将被更改。

QtGui.QFileDialog

QtGui.QFileDialog是允许用户选择文件或目录的对话框。 可以选择打开和保存文件。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we select a file with a
QtGui.QFileDialog and display its contents
in a QtGui.QTextEdit.

author: Jan Bodnar
website: zetcode.com 
last edited: October 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        self.textEdit = QtGui.QTextEdit()
        self.setCentralWidget(self.textEdit)
        self.statusBar()

        openFile = QtGui.QAction(QtGui.QIcon('open.png'), 'Open', self)
        openFile.setShortcut('Ctrl+O')
        openFile.setStatusTip('Open new File')
        openFile.triggered.connect(self.showDialog)

        menubar = self.menuBar()
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openFile)       

        self.setGeometry(300, 300, 350, 300)
        self.setWindowTitle('File dialog')
        self.show()

    def showDialog(self):

        fname = QtGui.QFileDialog.getOpenFileName(self, 'Open file', 
                '/home')

        f = open(fname, 'r')

        with f:        
            data = f.read()
            self.textEdit.setText(data) 

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

该示例显示了一个菜单栏,集中设置的文本编辑小部件和一个状态栏。 菜单项显示用于选择文件的QtGui.QFileDialog。 文件的内容被加载到文本编辑小部件中。

class Example(QtGui.QMainWindow):

    def __init__(self):
        super(Example, self).__init__()

该示例基于QtGui.QMainWindow小部件,因为我们集中设置了文本编辑小部件。

fname = QtGui.QFileDialog.getOpenFileName(self, 'Open file', 
        '/home')

我们弹出QtGui.QFileDialoggetOpenFileName()方法中的第一个字符串是标题。 第二个字符串指定对话框的工作目录。 默认情况下,文件过滤器设置为All Files (*)

f = open(fname, 'r')

with f:        
    data = f.read()
    self.textEdit.setText(data) 

读取所选文件名,并将文件内容设置为文本编辑小部件。

File Dialog

图:文件对话框

在 PyQt4 教程的这一部分中,我们使用了对话框。

PyQt4 小部件

原文: http://zetcode.com/gui/pyqt4/widgets/

小部件是应用的基本构建块。 PyQt4 有各种各样的小部件,包括按钮,复选框,滑块或列表框。 在本教程的这一部分中,我们将描述几个有用的小部件:QtGui.QCheckBoxToggleButtonQtGui.QSliderQtGui.QProgressBarQtGui.QCalendarWidget

QtGui.QCheckBox

QtGui.QCheckBox是具有两种状态的窗口小部件:开和关。 这是一个带有标签的盒子。 复选框通常用于表示可以启用或禁用的应用中的功能。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, a QtGui.QCheckBox widget
is used to toggle the title of a window.

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        cb = QtGui.QCheckBox('Show title', self)
        cb.move(20, 20)
        cb.toggle()
        cb.stateChanged.connect(self.changeTitle)

        self.setGeometry(300, 300, 250, 150)
        self.setWindowTitle('QtGui.QCheckBox')
        self.show()

    def changeTitle(self, state):

        if state == QtCore.Qt.Checked:
            self.setWindowTitle('QtGui.QCheckBox')
        else:
            self.setWindowTitle('')

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

在我们的示例中,我们将创建一个复选框,以切换窗口标题。

cb = QtGui.QCheckBox('Show title', self)

这是一个QtGui.QCheckBox构造器。

cb.toggle()

我们已经设置了窗口标题,因此我们还必须选中该复选框。 默认情况下,未设置窗口标题,并且未选中该复选框。

cb.stateChanged.connect(self.changeTitle)

我们将用户定义的changeTitle()方法连接到stateChanged信号。 changeTitle()方法将切换窗口标题。

def changeTitle(self, state):

    if state == QtCore.Qt.Checked:
        self.setWindowTitle('QtGui.QCheckBox')
    else:
        self.setWindowTitle('')

小部件的状态在state变量中提供给changeTitle()方法。 如果小部件被选中,我们设置窗口的标题。 否则,我们在标题栏设置一个空字符串。

QtGui.QCheckBox

图:QtGui.QCheckBox

ToggleButton

切换按钮是特殊模式下的QtGui.QPushButton。 它是具有两种状态的按钮:已按下和未按下。 我们通过单击在这两种状态之间切换。 在某些情况下此功能非常合适。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

In this example, we create three toggle buttons.
They will control the background color of a 
QtGui.QFrame. 

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        self.col = QtGui.QColor(0, 0, 0)       

        redb = QtGui.QPushButton('Red', self)
        redb.setCheckable(True)
        redb.move(10, 10)

        redb.clicked[bool].connect(self.setColor)

        greenb = QtGui.QPushButton('Green', self)
        greenb.setCheckable(True)
        greenb.move(10, 60)

        greenb.clicked[bool].connect(self.setColor)

        blueb = QtGui.QPushButton('Blue', self)
        blueb.setCheckable(True)
        blueb.move(10, 110)

        blueb.clicked[bool].connect(self.setColor)

        self.square = QtGui.QFrame(self)
        self.square.setGeometry(150, 20, 100, 100)
        self.square.setStyleSheet("QWidget { background-color: %s }" %  
            self.col.name())

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('Toggle button')
        self.show()

    def setColor(self, pressed):

        source = self.sender()

        if pressed:
            val = 255
        else: val = 0

        if source.text() == "Red":
            self.col.setRed(val)                
        elif source.text() == "Green":
            self.col.setGreen(val)             
        else:
            self.col.setBlue(val) 

        self.square.setStyleSheet("QFrame { background-color: %s }" %
            self.col.name())  

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

在我们的示例中,我们创建了三个切换按钮和一个QtGui.QWidget。 我们将QtGui.QWidget的背景色设置为黑色。 切换按钮将切换颜色值的红色,绿色和蓝色部分。 背景颜色取决于我们按下的切换按钮。

self.col = QtGui.QColor(0, 0, 0)    

这是初始的黑色值。

redb = QtGui.QPushButton('Red', self)
redb.setCheckable(True)
redb.move(10, 10)

要创建一个切换按钮,我们创建一个QtGui.QPushButton并通过调用setCheckable()方法使其可检查。

redb.clicked[bool].connect(self.setColor)

我们将clicked信号连接到用户定义的方法。 我们使用以布尔值操作的clicked信号。

source = self.sender()

我们得到被切换的按钮。

if source.text() == "Red":
    self.col.setRed(val)   

如果它是红色按钮,我们将相应地更新颜色的红色部分。

self.square.setStyleSheet("QFrame { background-color: %s }" %
    self.col.name())   

我们使用样式表来更改背景颜色。

ToggleButton

图:ToggleButton

QtGui.QSlider

QtGui.QSlider是具有简单句柄的小部件。 该手柄可以前后拉动。 这样,我们可以为特定任务选择一个值。 有时使用滑块比输入数字或使用旋转框更自然。

在我们的示例中,我们将显示一个滑块和一个标签。 这次标签将显示图像。 滑块将控制标签。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This example shows a QtGui.QSlider widget.

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)
        sld.setFocusPolicy(QtCore.Qt.NoFocus)
        sld.setGeometry(30, 40, 100, 30)
        sld.valueChanged[int].connect(self.changeValue)

        self.label = QtGui.QLabel(self)
        self.label.setPixmap(QtGui.QPixmap('mute.png'))
        self.label.setGeometry(160, 40, 80, 30)

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('QtGui.QSlider')
        self.show()

    def changeValue(self, value):

        if value == 0:
            self.label.setPixmap(QtGui.QPixmap('mute.png'))
        elif value > 0 and value <= 30:
            self.label.setPixmap(QtGui.QPixmap('min.png'))
        elif value > 30 and value < 80:
            self.label.setPixmap(QtGui.QPixmap('med.png'))
        else:
            self.label.setPixmap(QtGui.QPixmap('max.png'))

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

在我们的示例中,我们模拟了音量控制。 通过拖动滑块的手柄,我们可以更改标签上的图像。

sld = QtGui.QSlider(QtCore.Qt.Horizontal, self)

在这里,我们创建一个水平QtGui.QSlider

self.label = QtGui.QLabel(self)
self.label.setPixmap(QtGui.QPixmap('mute.png'))

我们创建一个QtGui.QLabel小部件并为其设置初始静音图像。

sld.valueChanged[int].connect(self.changeValue)

我们将valueChanged信号连接到用户定义的changeValue()方法。

if value == 0:
    self.label.setPixmap(QtGui.QPixmap('mute.png'))
...

基于滑块的值,我们将图像设置为标签。 在上面的代码中,如果滑块等于 0,则将mute.png图像设置为标签。

QtGui.QSlider widget

图:QtGui.QSlider widget

QtGui.QProgressBar

进度条是在处理冗长的任务时使用的小部件。 它具有动画效果,以便用户知道任务正在进行中。 QtGui.QProgressBar小部件在 PyQt4 工具包中提供了水平或垂直进度条。 程序员可以为进度条设置最小值和最大值。 默认值为 0 和 99。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This example shows a QtGui.QProgressBar widget.

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        self.pbar = QtGui.QProgressBar(self)
        self.pbar.setGeometry(30, 40, 200, 25)

        self.btn = QtGui.QPushButton('Start', self)
        self.btn.move(40, 80)
        self.btn.clicked.connect(self.doAction)

        self.timer = QtCore.QBasicTimer()
        self.step = 0

        self.setGeometry(300, 300, 280, 170)
        self.setWindowTitle('QtGui.QProgressBar')
        self.show()

    def timerEvent(self, e):

        if self.step >= 100:

            self.timer.stop()
            self.btn.setText('Finished')
            return

        self.step = self.step + 1
        self.pbar.setValue(self.step)

    def doAction(self):

        if self.timer.isActive():
            self.timer.stop()
            self.btn.setText('Start')

        else:
            self.timer.start(100, self)
            self.btn.setText('Stop')

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()    

在我们的示例中,我们有一个水平进度条和一个按钮。 该按钮将启动和停止进度条。

self.pbar = QtGui.QProgressBar(self)

这是一个QtGui.QProgressBar构造器。

self.timer = QtCore.QBasicTimer()

要激活进度条,我们使用一个计时器对象。

self.timer.start(100, self)

要启动计时器事件,我们调用其start()方法。 此方法有两个参数:超时和将接收事件的对象。

def timerEvent(self, e):

    if self.step >= 100:

        self.timer.stop()
        self.btn.setText('Finished')
        return

    self.step = self.step + 1
    self.pbar.setValue(self.step)

每个QtCore.QObject及其子代都有一个timerEvent()事件处理器。 为了对计时器事件做出反应,我们重新实现了事件处理器。

def doAction(self):

    if self.timer.isActive():
        self.timer.stop()
        self.btn.setText('Start')

    else:
        self.timer.start(100, self)
        self.btn.setText('Stop')

doAction()方法中,我们启动和停止计时器。

QtGui.QProgressBar

图:QtGui.QProgressBar

QtGui.QCalendarWidget

QtGui.QCalendarWidget提供基于月度的日历小部件。 它允许用户以简单直观的方式选择日期。

#!/usr/bin/python
# -*- coding: utf-8 -*-

"""
ZetCode PyQt4 tutorial 

This example shows a QtGui.QCalendarWidget widget.

author: Jan Bodnar
website: zetcode.com 
last edited: September 2011
"""

import sys
from PyQt4 import QtGui, QtCore

class Example(QtGui.QWidget):

    def __init__(self):
        super(Example, self).__init__()

        self.initUI()

    def initUI(self):      

        cal = QtGui.QCalendarWidget(self)
        cal.setGridVisible(True)
        cal.move(20, 20)
        cal.clicked[QtCore.QDate].connect(self.showDate)

        self.lbl = QtGui.QLabel(self)
        date = cal.selectedDate()
        self.lbl.setText(date.toString())
        self.lbl.move(130, 260)

        self.setGeometry(300, 300, 350, 300)
        self.setWindowTitle('Calendar')
        self.show()

    def showDate(self, date):     

        self.lbl.setText(date.toString())

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

该示例具有日历小部件和标签小部件。 当前选择的日期显示在标签窗口小部件中。

cal = QtGui.QCalendarWidget(self)

我们构造一个日历小部件。

cal.clicked[QtCore.QDate].connect(self.showDate)

如果我们从小部件中选择一个日期,则会发出clicked[QtCore.QDate]信号。 我们将此信号连接到用户定义的showDate()方法。

def showDate(self, date):     
    self.lbl.setText(date.toString())

我们通过调用selectedDate()方法检索所选日期。 然后,我们将日期对象转换为字符串并将其设置为标签小部件。

QtGui.QCalendarWidget

图:QtGui.QCalendarWidget

在 PyQt4 教程的这一部分中,我们介绍了几个小部件。