¿Cómo puedo mantener un wx.html.HtmlWindow en la misma posición durante el cambio de tamaño (sin parpadeo)?
Pregunta
Quiero ser capaz de cambiar el tamaño de una ventana HTML, pero mantener la posición de desplazamiento. Este ejemplo casi funciona, pero parpadea.
Para probar:
- cargar un archivo html buen tamaño con el botón Cargar archivo HTML
- desplazarse hacia abajo
-. Redimensionar la ventana
La ventana mantiene su misma posición a través cambia el tamaño, sino que parpadea horriblemente. El código para HTMLWindow está restableciendo la posición de desplazamiento para cambiar el tamaño de cada uno 0, creo. Me gustaría evitar que volver a dibujar hasta que la posición de desplazamiento se fija en la función post_resize.
He intentado varias combinaciones de congelación / descongelación y tratando de enganchar en los eventos de pintura, pero no han tenido éxito. Sugerencias?
import wx
import wx.html
_POST_RESIZE_EVENT = wx.NewEventType()
class _PostResizeEvent(wx.PyEvent):
def __init__(self, pos):
wx.PyEvent.__init__(self)
self.SetEventType(_POST_RESIZE_EVENT)
self.pos = pos
def EVT_POST_RESIZE(win, func):
win.Connect(-1, -1, _POST_RESIZE_EVENT, func)
class MyHtmlPanel(wx.Panel):
"""
class MyHtmlPanel inherits wx.Panel and adds a button and HtmlWindow
"""
def __init__(self, parent, id):
# default pos is (0, 0) and size is (-1, -1) which fills the frame
wx.Panel.__init__(self, parent, id)
self.SetDoubleBuffered(True)
self.SetBackgroundColour("yellow")
self.html1 = wx.html.HtmlWindow(self, id, pos=(0,30), size=(602,310))
self.btn1 = wx.Button(self, -1, "Load Html File", pos=(0,0))
self.btn1.Bind(wx.EVT_BUTTON, self.OnLoadFile)
self.btn2 = wx.Button(self, -1, "Clear Page", pos=(120,0))
self.btn2.Bind(wx.EVT_BUTTON, self.OnClearPage)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(self.btn1, 0)
sizer.Add(self.btn2, 0)
sizer.Add(self.html1, 1, wx.EXPAND)
self.SetSizer(sizer)
self.html1.Bind(wx.EVT_SCROLLWIN, self.OnScroll)
self.Bind(wx.EVT_SIZE, self.OnSize)
EVT_POST_RESIZE(self.html1, self.post_resize)
self.hist=0
def OnScroll(self, evt):
self.hist = self.html1.GetViewStart()[1]
evt.Skip()
def OnSize(self, evt):
wx.PostEvent(self.html1, _PostResizeEvent(self.hist))
evt.Skip()
def post_resize(self, evt):
self.html1.Scroll(0, evt.pos)
def OnLoadFile(self, event):
dlg = wx.FileDialog(self, wildcard = '*.html', style=wx.OPEN)
if dlg.ShowModal():
path = dlg.GetPath()
self.html1.LoadPage(path)
dlg.Destroy()
def OnClearPage(self, event):
self.html1.SetPage("")
app = wx.PySimpleApp()
# create a window/frame, no parent, -1 is default ID, title, size
frame = wx.Frame(None, -1, "HtmlWindow()", size=(610, 380))
# call the derived class, -1 is default ID
MyHtmlPanel(frame,-1)
# show the frame
frame.Show(True)
# start the event loop
app.MainLoop()
Solución
Yo esquivé el problema utilizando un ScrolledPanel (la cual no restablece sus posiciones scrollsbars' en el cambio de tamaño) y la representación directa del texto. (Que sólo estaba usando el panel html al texto de código de color).
La clase resultante:
import wx
import wx.lib.scrolledpanel as scrolledpanel
class ScrollableText(scrolledpanel.ScrolledPanel):
def __init__(self, parent, id=-1, text=[[('black', 'adsf')]]):
scrolledpanel.ScrolledPanel.__init__(self, parent, id)
self.SetBackgroundColour('white')
self.SetVirtualSize((1500, 1500))
self.SetupScrolling(True, True)
# a list of lists of (color, string) tuples
self.Bind(wx.EVT_PAINT, self.redraw)
self.Bind(wx.EVT_SIZE, self.resize)
# setup the font
self.font = wx.Font(10, wx.FONTFAMILY_MODERN, wx.NORMAL, wx.NORMAL)
if not self.font.IsFixedWidth():
self.font = wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.NORMAL, wx.NORMAL)
assert self.font.IsFixedWidth()
self.text = text
self.set_longest_line()
def set_text(self, text):
self.text = text
self.set_longest_line()
self.resize(None)
self.Refresh()
def set_longest_line(self):
if len(self.text) > 0:
self.longest_line = max([sum([len(s) for c, s in l]) for l in self.text])
else:
self.longest_line = 1
def draw_lines(self, DC, start, end, lineheight):
mw = 0
DC.SetFont(self.font)
for idx in range(max(0, start), min(end, len(self.text))):
l = self.text[idx]
combined = "".join([s[1] for s in l])
extents = [0] + DC.GetPartialTextExtents(combined)
for str in l:
DC.SetTextForeground(str[0])
DC.DrawText(str[1], extents[0], idx * lineheight)
extents = extents[len(str[1]):]
def resize(self, evt):
DC = wx.ClientDC(self)
DC.SetFont(self.font)
# find line width and height
extent = DC.GetFullTextExtent('X'*self.longest_line)
lineheight = extent[1]
maxwidth = extent[0]
# set virtual area
vsize = (maxwidth, len(self.text) * lineheight)
if self.GetVirtualSize() != vsize:
self.SetVirtualSize(vsize)
def redraw(self, evt):
DC = wx.PaintDC(self)
self.PrepareDC(DC)
extent = DC.GetFullTextExtent('x'*self.longest_line)
lineheight = extent[1]
vs = self.GetViewStart()
ppu = self.GetScrollPixelsPerUnit()
ri = wx.RegionIterator(self.GetUpdateRegion())
mmin, mmax = len(self.text), 0
while ri:
rect = ri.GetRect()
# find the lines that need rendering
min_y = rect[1] + vs[1] * ppu[1]
max_y = rect[1] + rect[3] + vs[1] * ppu[1]
min_line = int(min_y / lineheight) - 1
max_line = int(max_y / lineheight) + 2
mmin = min(min_line, mmin)
mmax = max(max_line, mmax)
ri.Next()
self.draw_lines(DC, mmin, mmax, lineheight)
if __name__ == '__main__':
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1)
t = [[('black', l[:len(l)/2]), ('red', l[len(l)/2:])] for l in open('scrollable_text.py')]
self.scrollable = ScrollableText(self, -1, t)
app = wx.PySimpleApp(None,-1)
frame = MainFrame(parent=None)
frame.Show()
app.MainLoop()