Добавьте горизонтальные и вертикальные полосы прокрутки в GUI Tkinter с несколькими кадрами [дублировать]

Распространенная ошибка.

string datetime = dataGridView1.Rows[i].Cells[2].Value.ToString();

Это неправильно! Если вы добавляете значения x в качестве строк, все они добавляются как 0, а Series не могут выровнять их с соответствующими слотами на оси X. Поэтому они добавляются слева направо последовательно.

Вместо этого просто добавьте x-значения в качестве DateTimes, которые они должны быть!

Итак, если Cells содержит DateTime используются:

DateTime datetime = (DateTime) dataGridView1.Rows[i].Cells[2].Value;

Если они этого не делают, преобразуйте их в DateTime

DateTime datetime = Convert.ToDateTime(dataGridView1.Rows[i].Cells[2].Value);

. Для управления типом значений x установите XValueType для каждой серии:

chart_dashboard.Series[yourSeries].XValueType = ChartValueType.DateTime;

Чтобы определить способ отображения этикеток оси, установите их форматирование:

chart_dashboard[ChartAreas[0].AxisX.LabelStyle.Format = someDateTimeFormatString;

Чтобы создать строку типа «Неделя 1», вы [

  • ]
    • установите XValueType на int16
    • , добавьте значение x в качестве чисел недели
    • , как ..axis.LabelStyle.Format = "Week #0";

    Чтобы извлечь номер из ваших данных, разделенных пробелом, и Convert.ToInt16!


    Если вам действительно нужно вставлять разреженные значения x в виде строк, для вставки манекена DataPoint в каждый промежуток в серии.

    Создание манекена DataPoint прост:

     DataPoint dp = new DataPoint() { IsEmpty = true};
    

    Но знание того, где пробелы заранее - это вызов! «Лучший» способ - передать данные и заполнить их перед добавлением очков. Или перейдите к нему позже, а не добавьте, вставив думмы в промежутки. И это гораздо больше проблем, чем получение данных в первую очередь!

  • 47
    задан Bryan Oakley 11 December 2015 в 01:04
    поделиться

    2 ответа

    Обзор

    Вы можете связывать полосы прокрутки только с несколькими виджетами, а корневой виджет и Frame не являются частью этой группы виджетов.

    Наиболее распространенное решение - создать виджет холста и связать полосы прокрутки с этим виджетами. Затем в этот холст встроен фрейм, содержащий виджеты ярлыков. Определите ширину / высоту кадра и отправьте это в параметр canvas scrollregion, чтобы область прокрутки точно соответствовала размеру кадра.

    Рисование текстовых элементов непосредственно на холсте не очень сложно , поэтому вы можете захотеть пересмотреть этот подход, если решение, встроенное в рамку в виде холста, кажется слишком сложным. Поскольку вы создаете сетку, координаты каждого текстового элемента будут очень легко вычисляться, особенно если каждая строка имеет одинаковую высоту (что, вероятно, это, если вы используете один шрифт).

    Для рисования непосредственно на холсте просто определите высоту строки шрифта, который вы используете (и для этого есть команды). Тогда каждая координата y row*(lineheight+spacing). Координата x будет фиксированным числом, основанным на самом широком элементе в каждом столбце. Если вы дадите все тег для столбца, в котором он находится, вы можете настроить координату и ширину x всех элементов в столбце с помощью одной команды.

    Объектно-ориентированное решение

    Вот пример решения, встроенного в рамку, с использованием объектно-ориентированного подхода:

    import tkinter as tk
    
    class Example(tk.Frame):
        def __init__(self, root):
    
            tk.Frame.__init__(self, root)
            self.canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
            self.frame = tk.Frame(self.canvas, background="#ffffff")
            self.vsb = tk.Scrollbar(root, orient="vertical", command=self.canvas.yview)
            self.canvas.configure(yscrollcommand=self.vsb.set)
    
            self.vsb.pack(side="right", fill="y")
            self.canvas.pack(side="left", fill="both", expand=True)
            self.canvas.create_window((4,4), window=self.frame, anchor="nw", 
                                      tags="self.frame")
    
            self.frame.bind("<Configure>", self.onFrameConfigure)
    
            self.populate()
    
        def populate(self):
            '''Put in some fake data'''
            for row in range(100):
                tk.Label(self.frame, text="%s" % row, width=3, borderwidth="1", 
                         relief="solid").grid(row=row, column=0)
                t="this is the second column for row %s" %row
                tk.Label(self.frame, text=t).grid(row=row, column=1)
    
        def onFrameConfigure(self, event):
            '''Reset the scroll region to encompass the inner frame'''
            self.canvas.configure(scrollregion=self.canvas.bbox("all"))
    
    if __name__ == "__main__":
        root=tk.Tk()
        Example(root).pack(side="top", fill="both", expand=True)
        root.mainloop()
    

    Процедурное решение

    Вот решение, которое не использует объекты :

    import tkinter as tk
    
    def populate(frame):
        '''Put in some fake data'''
        for row in range(100):
            tk.Label(frame, text="%s" % row, width=3, borderwidth="1", 
                     relief="solid").grid(row=row, column=0)
            t="this is the second column for row %s" %row
            tk.Label(frame, text=t).grid(row=row, column=1)
    
    def onFrameConfigure(canvas):
        '''Reset the scroll region to encompass the inner frame'''
        canvas.configure(scrollregion=canvas.bbox("all"))
    
    root = tk.Tk()
    canvas = tk.Canvas(root, borderwidth=0, background="#ffffff")
    frame = tk.Frame(canvas, background="#ffffff")
    vsb = tk.Scrollbar(root, orient="vertical", command=canvas.yview)
    canvas.configure(yscrollcommand=vsb.set)
    
    vsb.pack(side="right", fill="y")
    canvas.pack(side="left", fill="both", expand=True)
    canvas.create_window((4,4), window=frame, anchor="nw")
    
    frame.bind("<Configure>", lambda event, canvas=canvas: onFrameConfigure(canvas))
    
    populate(frame)
    
    root.mainloop()
    

    Примечание: чтобы эта работа выполнялась в python 2.x, используйте Tkinter, а не tkinter в инструкции import

    85
    ответ дан Bryan Oakley 20 August 2018 в 07:03
    поделиться
    • 1
      Я сейчас пытаюсь это сделать. Для начала я просто загружаю данные в фрейм, а затем помещаю рамку в холст, но окно не подбирает размер к холсту, а параметры сетки для определения геометрии кадра не работают. Если я сделаю какой-либо прогресс, я опубликую обновление. – Simon Hibbs 23 June 2010 в 15:08
    • 2
      @Simon Hibbs: Я добавил пример, иллюстрирующий, как это сделать. – Bryan Oakley 23 June 2010 в 16:59
    • 3
      Это отлично, спасибо. У меня это работает хорошо. Какова цель переменных ширины и высоты в OnFrameConfigure? Они в настоящее время не используются, и окно приложения не устанавливается на границы холста, что я ожидаю. Это просто модно, хотя, спасибо за вашу помощь. – Simon Hibbs 23 June 2010 в 18:06
    • 4
      @DaniGehtdichnixan: вы можете создать привязку на холсте для события <Configure>, которое будет срабатывать при изменении размера холста. В обработчике событий вы можете настроить атрибут minsize одного из столбцов, чтобы он заполнил весь холст (например: self.frame.columnconfigure(1, minsize=SIZE), где вы делаете небольшую математику для вычисления SIZE). – Bryan Oakley 13 May 2013 в 18:37
    • 5
      @martineau: да, в этом конкретном случае он отлично работает, поскольку содержимое внутреннего кадра никогда не изменяется. Однако в качестве общего решения использование привязки будет охватывать случай, когда в кадре будет добавлено больше виджетов или если виджеты во внутреннем фрейме меняются. Хотя, честно говоря, этот пример нуждается в _additional_binding в событии <Configure> самого холста, чтобы обрабатывать случай, когда он изменяется. – Bryan Oakley 8 April 2018 в 16:46

    Сделать это прокручиваемым

    Используйте этот удобный класс, чтобы прокручивать фрейм, содержащий ваши виджеты. Выполните следующие шаги:

    1. создать кадр
    2. отобразить его (пакет, сетка и т. Д.)
    3. сделать его прокручиваемым
    4. добавить в него виджеты
    5. вызвать метод update ()

    import tkinter as tk
    from tkinter import ttk
    
    class Scrollable(ttk.Frame):
        """
           Make a frame scrollable with scrollbar on the right.
           After adding or removing widgets to the scrollable frame, 
           call the update() method to refresh the scrollable area.
        """
    
        def __init__(self, frame, width=16):
    
            scrollbar = tk.Scrollbar(frame, width=width)
            scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)
    
            self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
            self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
    
            scrollbar.config(command=self.canvas.yview)
    
            self.canvas.bind('<Configure>', self.__fill_canvas)
    
            # base class initialization
            tk.Frame.__init__(self, frame)         
    
            # assign this obj (the inner frame) to the windows item of the canvas
            self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)
    
    
        def __fill_canvas(self, event):
            "Enlarge the windows item to the canvas width"
    
            canvas_width = event.width
            self.canvas.itemconfig(self.windows_item, width = canvas_width)        
    
        def update(self):
            "Update the canvas and the scrollregion"
    
            self.update_idletasks()
            self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))
    

    Пример использования

    root = tk.Tk()
    
    header = ttk.Frame(root)
    body = ttk.Frame(root)
    footer = ttk.Frame(root)
    header.pack()
    body.pack()
    footer.pack()
    
    ttk.Label(header, text="The header").pack()
    ttk.Label(footer, text="The Footer").pack()
    
    
    scrollable_body = Scrollable(body, width=32)
    
    for i in range(30):
        ttk.Button(scrollable_body, text="I'm a button in the scrollable frame").grid()
    
    scrollable_body.update()
    
    root.mainloop()
    
    0
    ответ дан Tarqez 20 August 2018 в 07:03
    поделиться
    • 1
      Я получаю ошибку в привязке, что она не привязана ни к чему, поэтому я попробовал ее с помощью & quot; & lt; Configure & gt; & quot ;. Тогда он работает, но заканчивается неправильным размером ... как сделать прокручиваемую рамку заполнять все пространство родителя и динамически изменять размер? – samkass 10 February 2018 в 22:20
    • 2
      Спасибо @samkass, добавив & quot; & lt; Configure & gt; & quot; правильно, это была опечатка. – Tarqez 11 February 2018 в 11:29
    • 3
      Добавить параметры: body.pack (fill = tk.BOTH, expand = True) – Tarqez 26 March 2018 в 18:03
    Другие вопросы по тегам:

    Похожие вопросы: