Skip to content

This example will show you how to remove the Windows title bar and replace it with a custom title bar. The custom title bar will contain buttons for minimize, maximize, window, and close. As of 1/28/2020, Kivy's framework does not provide support for creating a custom title bar on its own without special modifications to the source code.

Notifications You must be signed in to change notification settings

jcradzwill/Create-custom-title-bar-in-Kivy

Repository files navigation

------------High Level Overview------------

github link: https://github.com/jcradzwill/Create-custom-title-bar-in-Kivy

As of 1/28/2020, the base Kivy framework does not naturally support custom title bar features without the implementation of special workaround code. This workaround example will show you how to remove the Windows title bar and replace it with a custom title bar with minimize, window, maximize, close, window dragging, and window resizing features in Windows OS. The custom title bar will contain buttons for minimize, maximize, window, and close in the top right corner of the App. 

Please note that this workaround example will ONLY work on Windows OS since it involves Windows API code modifications. It was built and tested on a PC using Windows 10 OS. If you want to make this work on Mac OS, you will need to implement similar code using the Mac OS APIs. 

This example utilizes pynput for the Window dragging and resize functionality. You can easily install pynput using pip. We use pynput instead of Kivy's on_touch_move method because it prevents the window from 'skipping' around when it is dragged and resized as this is very annoying and not user friendly. 

This example also utilizes UI Automation. You can easily install uiautomation using pip. We use UI Automation to get around a bug in the Kivy source code that prevents the App from minimizing/maximizing when the app icon is clicked in the taskbar while Window.borderless is set to 1. I have made a post about this bug on github here: kivy/kivy#6707 and on stackoverflow here: https://stackoverflow.com/questions/59910143/cant-minimize-kivy-window-when-borderless-1-border-disabled

It would be really nice to get this bug fixed because it is very difficult to find and execute OS API functions that monitor clicks on app icons in the taskbar!

------------github file Overview------------

Please see below for an overview of each of the files uploaded to github to support this example:

1) __init__.py - The Window Kivy source code file that has been modified to make this custom title bar example work. You can find this file locally on your machine here: Python37-32\Lib\site-packages\kivy\core\window\__init__.py. All code changes to this file are marked with a comment tag of #NEW CODE HERE within the file. I have specific instructions on how to modify this file in the steps below. However, it would be much easier for you to simply replace YOUR existing Kivy source code file with this one. I would recommend backing up the existing file before replacing it with this one just to be safe. 

2) uiautomation.py - The uiautomation source code file that has been modified to make this custom title bar example work. After you pip install uiautomation, you can find the file locally here: Python37-32\Lib\site-packages\uiautomation\uiautomation.py. All code changes to this file are marked with a comment tag of #NEW CODE HERE within the file. I have specific instructions on how to modify this file in the steps below. However, it would be much easier for you to simply replace YOUR existing uiatuomation file with this one. I would recommend backing up the existing file before replacing it with this one just to be safe. 

3) test.py - This is the custom title bar test app that should run properly once you implement the code changes to the Kivy source file and uiautomation file mentioned above. It should launch a Kivy App that has minimize, maximize, window, and close buttons in the top right corner that are clickable. You should also be able to move the App window around and resize it on the edges. 

4) close_icon_blue.png, maximize_icon_blue.png, minimize_icon_blue.png, and window_icon_blue.png - These are the png images being used for the minimize, maximize, window, and close buttons in the top right corner of the test App. Make sure you place these files in the same folder as the test.py file when you run it.

------------Kivy Window Source Code File Modification Steps------------

Please note that these Kivy source code changes and this example will ONLY work on Windows OS since it involves Windows API code additions.

The first set of steps below involve updating the Window Kivy source code located here: Python37-32\Lib\site-packages\kivy\core\window\__init__.py. You can open the Window Kivy source code file using an IDE to modify the code.

1) Within the Window Kivy source code file, there are import statements at the top. Insert these new import statements (see where it says NEW CODE HERE):

from os.path import join, exists
from os import getcwd

from kivy.core import core_select_lib
from kivy.clock import Clock
from kivy.config import Config
from kivy.logger import Logger
from kivy.base import EventLoop, stopTouchApp
from kivy.modules import Modules
from kivy.event import EventDispatcher
from kivy.properties import ListProperty, ObjectProperty, AliasProperty, \
    NumericProperty, OptionProperty, StringProperty, BooleanProperty
from kivy.utils import platform, reify, deprecated
from kivy.context import get_current_context
from kivy.uix.behaviors import FocusBehavior
from kivy.setupconfig import USE_SDL2
from kivy.graphics.transformation import Matrix
from kivy.graphics.cgl import cgl_get_backend_name
from win32api import GetMonitorInfo #NEW CODE HERE - NOTE: THIS CODE WILL ONLY WORK IN WINDOWS SINCE IT USES WINDOWS API FUNCTIONS
import win32api #NEW CODE HERE
 
2) Within the WindowBase class, we need to create and populate some new variables. Basically, these variables are being used to capture screen position and screen size of all active monitors with and without taskbars. 

class WindowBase(EventDispatcher):

    #NEW CODE HERE - NOTE: THIS CODE WILL ONLY WORK IN WINDOWS SINCE IT USES WINDOWS API FUNCTIONS
    monitors = win32api.EnumDisplayMonitors() #fetch all of the monitor handles..
    monitor_data_excl_taskbar = []
    monitor_data_incl_taskbar = []
    for x in range(0, len(monitors)): #loop through the monitors and store data about them..
        #store all monitor data EXCLUDING the taskbars..
        monitor_info = GetMonitorInfo(monitors[x][0]).get("Work") 
        x1_pos = monitor_info[0]
        x2_pos = monitor_info[2]
        y1_pos = monitor_info[1]
        y2_pos = monitor_info[3]
        screen_width = x2_pos - x1_pos
        screen_height = y2_pos - y1_pos
        monitor_data_excl_taskbar.append([x1_pos, x2_pos, y1_pos, y2_pos, screen_width, screen_height])
        #store all monitor data INCLUDING the taskbars..
        monitor_info = GetMonitorInfo(monitors[x][0]).get("Monitor")
        x1_pos = monitor_info[0]
        x2_pos = monitor_info[2]
        y1_pos = monitor_info[1]
        y2_pos = monitor_info[3]
        screen_width = x2_pos - x1_pos
        screen_height = y2_pos - y1_pos
        monitor_data_incl_taskbar.append([x1_pos, x2_pos, y1_pos, y2_pos, screen_width, screen_height])
    disable_on_restore = 'N'
    maximize_trigger = 'N'
    original_screen_pos_left = monitor_data_excl_taskbar[0][0] #get the x position of the primary screen's resolution excluding taskbars
    original_screen_pos_top = monitor_data_excl_taskbar[0][2] #get the y position of the primary screen's resolution excluding taskbars
    original_screen_width = monitor_data_excl_taskbar[0][4] #get the width of the primary screen's resolution excluding taskbars
    original_screen_height = monitor_data_excl_taskbar[0][5] #get the height of the primary screen's resolution excluding taskbars
    last_screen_pos_left = original_screen_pos_left
    last_screen_pos_top = original_screen_pos_top
    last_screen_width = original_screen_width 
    last_screen_height = original_screen_height
    window_state = 'open'

    '''WindowBase is an abstract window widget for any window implementation.

3) Locate the 'def on_restore' method and modify it to the following:

    def on_restore(self, *largs):
        '''Event called when the window is restored.

        .. versionadded:: 1.10.0

        .. note::
            This feature requires the SDL2 window provider.
       
        '''
        #NEW CODE HERE
        if Window.disable_on_restore == 'N': #we have to disable this on_restore event from running when the app size is reduced using windowed button
            Window.left = Window.last_screen_pos_left
	    Window.top = Window.last_screen_pos_top
            Window.size = [Window.last_screen_width, Window.last_screen_height]
        pass

------------uiautomation Source Code File Modification Steps------------

The next set of steps below involve updating the uiautomation source code located here: Python37-32\Lib\site-packages\uiautomation\uiautomation.py. You can open the uiautomation source code file using an IDE to modify the code.

1) Locate the existing 'def ControlFromPoint' method in the file and add these 3 new methods below it (see where it says NEW CODE HERE):

def ControlFromPoint(x: int, y: int) -> Control:
    """
    Call IUIAutomation ElementFromPoint x,y. May return None if mouse is over cmd's title bar icon.
    Return `Control` subclass or None.
    """
    element = _AutomationClient.instance().IUIAutomation.ElementFromPoint(ctypes.wintypes.POINT(x, y))
    return Control.CreateControlFromElement(element)

#NEW CODE HERE
def GetElementFromPoint(x: int, y: int) -> Control: #gets the element from a point (x, y)
    element = _AutomationClient.instance().IUIAutomation.ElementFromPoint(ctypes.wintypes.POINT(x, y))
    return element

#NEW CODE HERE
def GetParentElement(element): #gets the parent of an element input
    parent_element = _AutomationClient.instance().ViewWalker.GetParentElement(element)
    return parent_element

#NEW CODE HERE
def GetElementInfo(element): #gets the property information about an element
    element_info = Control.CreateControlFromElement(element)
    return element_info


------------Running the Test App------------

Once you have completed the Kivy source code and uiautomation source code modifications mentioned above, you should be able to run the test App (test.py file) with no problems. 

Locate the example test.py file, run it, and see what it does. Within this test app, there are specific methods that are called to account for the user minimizing, maximizing, windowing, and closing the window using the buttons at the top and clicking the python icon in the taskbar. You should also be able to 'window' the app, move it around, and resize it.

Within the test app's code, you will notice that we utilize the newly created variables that we created in the source code modification steps above. Since the native Windows title bar is gone, we must account for all aspects of the user manipulating the window using python: minimize, maximize, window, close, window dragging, and window resizing.

------------Future Development------------

I will start working on the custom title bar workaround code for Mac OS next. Stay tuned! 

About

This example will show you how to remove the Windows title bar and replace it with a custom title bar. The custom title bar will contain buttons for minimize, maximize, window, and close. As of 1/28/2020, Kivy's framework does not provide support for creating a custom title bar on its own without special modifications to the source code.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages