2

I want to display a custom icon in a PyQt window after freezing the baseline with cx_Freeze. The icon displays fine when the unfrozen script is executed from within the IDE (Spyder, for me). I'm using PyQt5, Python 3.6, and Windows 10. Here is my Python script (IconTest.py) that creates a main window and shows the path to the icon and whether the path exists. The icon file needs to be in the same directory as IconTest.py:

import sys, os
from PyQt5.QtWidgets import QApplication, QWidget, QLabel
from PyQt5.QtGui import QIcon

class Example(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):        
        self.setGeometry(200, 300, 600, 100)
        if getattr(sys, 'frozen', False): #If frozen with cx_Freeze
            self.homePath = os.path.dirname(sys.executable)
        else: # Otherwise, if running as a script (e.g., within Spyder)
            self.homePath = os.path.dirname(__file__)
        self.iconFileName = os.path.join(self.homePath, 'myIcon.ico')
        self.setWindowIcon(QIcon(self.iconFileName))        
        self.setWindowTitle('Icon')
        self.label1 = QLabel(self)
        self.label2 = QLabel(self)
        self.label1.move(10, 20)
        self.label2.move(10, 40)
        self.label1.setText("Path to icon file: " + str(self.iconFileName))
        self.label2.setText("Does file exit?  " + str(os.path.exists(self.iconFileName)))
        self.show()

if __name__ == '__main__':    
    app = QApplication(sys.argv)
    ex = Example()
    sys.exit(app.exec_())

Here is my result when running the script from within Spyder (unfrozen). As you can see, there is an icon displayed that resembles a stopwatch: enter image description here

Here is my setup.py for creating the frozen baseline:

from cx_Freeze import setup, Executable
import os, sys

exeDir = os.path.dirname(sys.executable)
platformsPath = os.path.join(exeDir, "Library\\Plugins\\Platforms\\")
iconPath = os.path.join(os.path.dirname(__file__), "myIcon.ico")
exe=Executable(script="IconTest.py", base = "Win32GUI", icon = iconPath)
includes=[iconPath, platformsPath]
excludes=[]
packages=[]
setup(
     version = "0.1",
     description = "My Icon Demo",
     options = {'build_exe': {'excludes':excludes,'packages':packages,'include_files':includes}},
     executables = [exe]
     )

Here is my result when running the frozen script (the executable in the build directory). As you can see, the stopwatch icon is replaced with a generic windows icon:

enter image description here

Suggestions?

slalomchip
  • 779
  • 2
  • 9
  • 25
  • Instead of creating several post with the same question you must improve your initial question or at least delete it – eyllanesc Oct 17 '18 at 19:36
  • I flagged the other question for a moderator to delete. I can't delete it because it was answered. The question and responses were heading in a wrong direction, so I felt compelled to completely rewrite the question with minimal, complete, and viable code. – slalomchip Oct 17 '18 at 21:45
  • 1
    Was the icon actually copied to the build folder? – Jannick Oct 18 '18 at 08:45
  • @slalomchip I have deleted my answer to your other question so that you can delete it. – jpeg Oct 18 '18 at 19:03
  • @Jannick - Yes, the icon file was actually copied to the `build` folder. Otherwise, the second line in the window would have been `False` instead of `True` – slalomchip Oct 20 '18 at 12:18
  • @slalomchip Great that you found the solution and that you share it with us. I would recommend that you post your solution as a standalone answer post to your question and mark this answer as accepted, instead of adding it to the body of your question as you did. To my opinion it would be clearer for future visitors, and one can decide to vote up your question or your answer (or both!) depending on what one finds helpful. – jpeg Nov 05 '18 at 07:04

3 Answers3

1

Interesting question and nice minimal example. After some searching I guess it could have to do with PyQt5 missing a plugin/DLL to display .ico image files in the frozen application. See e.g. How to load .ico files in PyQt4 from network.

If this is true, you have 2 options:

  1. Try the same example with a .png file as window icon

  2. If the plugins directory is included in the frozen application but it cannot find it, try to add the following statements

    pyqt_dir = os.path.dirname(PyQt5.__file__)
    QApplication.addLibraryPath(os.path.join(pyqt_dir, "plugins"))`
    

    before

    app = QApplication(sys.argv)
    

    in your main script. See this answer.

    If the plugins directory is not included in the frozen application, you need to tell cx_Freeze to include it using the include_files entry of the build_exe option. Either you manage to dynamically let your setup script include it at the place where PyQt5 is looking for it, using a tuple (source_path_to_plugins, destination_path_to_plugins) in include_files, or you tell PyQt5 where to look for it, using QApplication.addLibraryPath.

    In your previous question to this issue you actually had an entry to include a Plugins\\Platforms directory in your setup script, maybe you simply need to repair this include. Please note that cx_Freeze version 5.1.1 (current) and 5.1.0 move all packages into a lib subdirectory of the build directory, in contrary to the other versions.

jpeg
  • 2,372
  • 4
  • 18
  • 31
  • (1) Trying a PNG file made no difference. (2) I found that I need to add the `Platforms` directory. I guess I never deleted it from the `exe.win-amd64-3.6` directory when minimizing `setup.py`. I corrected the script in my question above. (3) I am using cx_Freeze 5.1.1. I think you are probably correct that there is a missing .dll file somewhere. I have tried different adds to the `exe.win-amd64-3.6` directory and to the `lib` directory. The `Platform` directory has to be added to `exe.win-amd64-3.6`, not `lib`. Still searching for the correct solution - haven't found it yet. – slalomchip Oct 20 '18 at 12:16
  • @slalomchip Have you tried with `packages = ['atexit', 'PyQt5']` in the the setup script? – jpeg Oct 24 '18 at 05:26
  • I included `packages = ['atexit', 'PyQt5']` in the setup script with no success. The `PyQt5` directory in the resultant `lib` directory was the same (148 files in 6 directories) whether `PyQt5` was in the `packages` list or not. I don't understand the purpose of adding `atexit` to the `package` list unless I'm incorporating `atexit` in my icon script (which I have not done). Is there a reason you suggested including `atexit` in the `package` list? Still searching. – slalomchip Oct 26 '18 at 00:21
  • @slalomchip To be more specific: 1. Try to find out which DLL interpretes .ico images in your python installation, it should be something like `lib\site-packages\PyQt5\Qt\plugins\imageformats\qico.dll`; 2. Check whether this DLL is included by `cx_Freeze`in the resultant `lib` directory and where, it should be in some `plugins\imageformats`directory; my guess was that adding `PyQt5` to the `packages` list tells `cx_Freeze` to include the whole `PyQt5` directory; 3. Tell your application where to find this `plugins` directory using `QApplication.addLibraryPath`. – jpeg Oct 26 '18 at 19:52
  • @slalomchip The reason I suggested including `atexit` in the `packages` list is that it is done so in the `PyQt5` example setup script in `cx_Freeze`, see `lib\site-packages\cx_Freeze\samples\PyQt5`; to be honest I don't understand the purpose either. – jpeg Oct 26 '18 at 19:58
  • I have been digging and digging into this looking for the solution. I believe all classes from the `PyQt5.QtGui` module are embedded in the files `Qt5Gui.dll` and `QtGui.pyd`. This convention seems to be consistent across all modules and classes because all of the frozen dlls appear to be at the module level, not at the class level. All other functionality of my larger frozen script work fine. I forgot the reason I chose cx_Freeze over the other packages, but I might look again at some of the others (`pyInstaller`, `py2exe`, etc.) or live with the default icon. Thanks for all of your help. – slalomchip Oct 27 '18 at 12:16
  • @slalomchip Strange issue. All I can say is that I'm able to use window icons in applications frozen with `cx_Freeze` using `PySide` (based on `Qt4`). – jpeg Oct 27 '18 at 20:32
  • I found the issue - it evidently is the way Anaconda packages its content. I have been using the Anaconda platform and read in other posts that there is an incompatibility between PyInstaller and Anaconda. I Installed Python on another machine and the frozen script displayed the correct icon. I will answer the question. Thanks for racking your brain with me on this. – slalomchip Nov 03 '18 at 18:44
1

ANSWER: I have been using the Anaconda platform and read in other posts that there are issues between PyInstaller and Anaconda because of the way Anaconda structures its content. Thinking the same issue might exist with cx_Freeze, I installed Python (no Anaconda) on a different machine and froze the script from this new Python installation. The icon appeared as expected in the frozen script. To make the icon display properly, I made the following changes to the setup.py script:

  1. Removed import sys
  2. Removed the line exeDir = ...
  3. Removed the line platformsPath = ...
  4. Removed platformsPath from the includes = list
slalomchip
  • 779
  • 2
  • 9
  • 25
0

I had a similar problem and posted an answer here.

Basically, I have overcome this issue by storing the file in a python byte array and loading it via QPixmap into a QIcon.

Saren Tasciyan
  • 454
  • 4
  • 15