【问题标题】:How to update kivy progress bar value from new thread?如何从新线程更新 kivy 进度条值?
【发布时间】:2021-12-29 19:18:30
【问题描述】:

我有这个图像下载器,它作为新线程和一个包含进度条的弹出窗口工作。下载期间进度条不会更新,但之后(下载器是用请求编写的,gui 应用程序是用 kivy 制作的)。任何想法如何解决这个问题?

下载器: 它被分隔在另一个文件中

class Downloader(threading.Thread):

    def __init__(self, url: str, download_monitor):
        super(Downloader, self).__init__(daemon=True)  # daemon dies when main die
        self.url = url
        self.download_monitor = download_monitor  # url popup

    def run(self) -> None:
        # Reset
        self.download_monitor.reset()

        file_name = self.url.split('/')[-1]

        # Less RAM usage
        with requests.get(self.url, stream=True) as req:  # stream=True not to read at once
            req.raise_for_status()
            with open('temp/'+file_name, 'wb') as file:
                chunks = list(enumerate(req.iter_content(chunk_size=8192)))
                self.download_monitor.downloading_progress.max = chunks[-1][0]  # last element
                for progress, chunk in chunks:
                    self.download_monitor.downloading_progress.value = progress
                    file.write(chunk)

弹出 .py: 它被分隔在另一个文件中

class UrlPopup(Popup):
    url_input = ObjectProperty()
    downloading_progress = ObjectProperty()

    def __init__(self, **kwargs):
        super(UrlPopup, self).__init__(**kwargs)
        
    def download(self):
        # https://www.nasa.gov/sites/default/files/thumbnails/image/hubble_ngc2903_potw2143a.jpg.jpg
        if self.url_input.text.startswith('https://'):  # if it is url address
            download(self.url_input.text, self)

    def on_dismiss(self):
        self.reset()
        self.url_input.text = ''

    def reset(self):
        self.downloading_progress.max = 0
        self.downloading_progress.value = 0

弹出.kv: 它被分隔在另一个文件中

<UrlPopup>:
    url_input: url_input
    downloading_progress: downloading_progress

    id: downloader
    title: 'URL address'
    size_hint: .25, None
    height: 157

    BoxLayout:
        orientation: 'vertical'
        size_hint_y: None
        height: 64

        TextInput:
            id: url_input

            multiline: False
            size_hint_y: None
            height: 32
            font_size: 16

        ProgressBar:
            id: downloading_progress

            size_hint_y: None
            height: 32

        BoxLayout:
            orientation: 'horizontal'
            size_hint_y: None
            height: 32

            Button:
                text: 'Download'
                on_press: root.download()
            Button:
                text: 'Close'
                on_press: root.dismiss()

EDIT1 ApuCoder 我按照你写的做了,但下载后进度仍然更新。 还有其他想法吗? 弹出 .py

class UrlPopup(Popup):
    url_input = ObjectProperty()
    downloading_progress = ObjectProperty()
    progress_value = NumericProperty()

    def update_progress(self, dt):
        self.progress_value += 1

下载器.py

 with requests.get(self.url, stream=True) as req:  # stream=True not to read at once
            req.raise_for_status()
            with open('temp/'+file_name, 'wb') as file:
                chunks = list(enumerate(req.iter_content(chunk_size=8192)))
                self.download_monitor.downloading_progress.max = chunks[-1][0]  # last element
                Clock.schedule_interval(self.download_monitor.update_progress, .1)
                for progress, chunk in chunks:
                    #self.download_monitor.downloading_progress.value = progress
                    file.write(chunk)

弹出.kv

ProgressBar:
            id: downloading_progress

            value: root.progress_value
            size_hint_y: None
            height: 32

EDIT2 这与类下载器在同一个文件中。我在按下按钮时调用此函数

def download(url: str, download_monitor):
    """Other thread"""
    downloader = Downloader(url, download_monitor)
    downloader.start()

【问题讨论】:

  • 一种方法是,创建一个NumericProperty,例如“progress_val”,并将其传递给PopUp .kv 内的ProgressBar.value。现在安排 (Clock.schedule_interval) 回调并在间隔后(例如,1/10 秒)不断更新此值。
  • @ApuCoder check EDIT1 你有这个想法吗?
  • 从你的代码中不清楚,你在哪里开始线程Downloader。如果您不更新服务内的属性,您将在最后获得更新的值。我认为,在线程等内部更新或在外部安排回调可能会有所帮助。
  • @ApuCoder 现在是否清楚我在哪里开始线程Downloader EDIT2?感谢您的帮助

标签: python python-3.x kivy kivy-language python-3.9


【解决方案1】:

假设您想下载一些内容并在 kivy 中显示正在进行的过程(或当前状态),我更新并修改了您的一些代码以做一个最小的示例。

在这种情况下,不需要创建一个新的Thread类,而是每次都创建一个新的线程对象并将target设置为一些方法(这里是start_download)来获取和写入二进制数据在磁盘中。因此,可以在此方法中控制进度,因此不需要调度。

from threading import Thread
import requests

from kivy.app import runTouchApp
from kivy.lang import Builder
from kivy.properties import (
    BooleanProperty,
    NumericProperty,
    ObjectProperty,
)
from kivy.uix.popup import Popup
from kivy.uix.screenmanager import Screen



Builder.load_string("""

<DownLoadScreen>:

    Button:
        text: "Open Downloader"
        on_release: root.open_downloader()


<UrlPopup>:
    url_input: url_input
    title: 'URL address'
    size_hint: .75, None
    height: "450dp"

    BoxLayout:
        orientation: "vertical"

        TextInput:
            id: url_input
            text: "https://www.nasa.gov/sites/default/files/thumbnails/image/hubble_ngc2903_potw2143a.jpg.jpg"
            multiline: False
            size_hint_y: None
            height: "64dp"
            font_size: "16sp"

        ProgressBar:
            pos_hint: {"center_x" : 0.5}
            value: root.prog_val
            max: root.tot_size

        Label:
            id: lbl
            text: "Downloading file...({:.0%})".format(root.prog_val/root.tot_size) if root.has_started else ""

        BoxLayout:
            size_hint_y: None
            height: dp(48)

            Button:
                text: 'Download'
                on_release: root.download()

            Button:
                text: 'Close'
                on_press: root.dismiss()
""")



class UrlPopup(Popup):

    url_input = ObjectProperty()
    prog_val = NumericProperty(0) # To capture the current progress.
    tot_size = NumericProperty(1) # Total size of the file/content. Setting the default value to 1 to avoid ZeroDivisionError, though will not affect anyhow.
    has_started = BooleanProperty(False) # Just to manipulate the label text.

    def start_download(self):
        self.has_started = True
        self.url = self.url_input.text
#       file_name = self.url.split('/')[-1]
        with requests.get(self.url, stream=True) as req:
            if req.status_code == 200: # Here, you can create the binary file.
#               chunks = list(enumerate(req.iter_content(chunk_size=8192))) # This may take more memory for larger file.
                self.tot_size = int(req.headers["Content-Length"])
                item_size = 2048 # Reducing the chunk size increases writing time and so needs more time in progress.
                for i, chunk in enumerate(req.iter_content(chunk_size = item_size)):
                    self.prog_val = i*item_size
#                   file.write(chunk)
                self.ids.lbl.text = "Download completed." # A confirmation message.

    def download(self):
        """A new thread object will be created each time this method is revoked. But be careful about the threads already created."""
        Thread(target = self.start_download).start()

    def on_dismiss(self):
        self.url_input.text = ""
        self.has_started = False



class DownLoadScreen(Screen):

    def open_downloader(self):
        UrlPopup().open()


runTouchApp(DownLoadScreen())

如果它符合您的需要,请告诉我。

【讨论】:

  • 现在很好用。谢谢
  • 在这种情况下,您可以检查此答案是否已接受(通过检查勾选标记),因此其他人也会发现它有帮助。
猜你喜欢
  • 2020-11-05
  • 1970-01-01
  • 2015-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-29
相关资源
最近更新 更多