Solved Redrawing GeUserArea in ScrollGroup with Slider Input

Hello,
I'm trying to resize a GeUserArea within a ScrollGroup with a gui Slider. The GeUserArea's border is not being redrawn properly in my script as I don't think I'm calling the Redraw() / LayoutChanged() in the correct way: it's drawing unwanted lines as I drag the slider and scrollbar.
Slider_Drag.png

I just want one solid border. The extra borders go away upon release of the Slider handle but are persistent with the scrollbar. Where would be the right place to call Redraw (or is something else causing the problem)? Thank you!

import c4d
from c4d import gui

class ExampleUserArea(c4d.gui.GeUserArea):
    def __init__(self, x, y, width, height):
        super(ExampleUserArea, self).__init__()
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.Redraw()

    def DrawMsg(self, x1, y1, x2, y2, msg) :
        self.OffScreenOn()
        self.SetClippingRegion(x1, y1, x2, y2)
        self.DrawRectangle(x1, y1, x2, y2)
        self.DrawBorder(c4d.BORDER_IN, x1, y1, x2, y2)

    def GetMinSize(self):
        return self.width, self.height

    def InputEvent(self, msg) :
        self.Redraw()
        return True     

class ExampleDialog(c4d.gui.GeDialog):
    ID_SLIDER = 10000
    ID_UA = 10001
    ID_SCROLLGRP = 10002

    def __init__(self):
        pass

    def Command(self, id, data):
        if id == self.ID_SLIDER:
            self.ua.height = int(self.GetFloat(self.ID_SLIDER))
            print(self.ua.width,self.ua.height)
            self.ua.LayoutChanged()
        return True

    def CreateLayout(self):
        self.SetTitle("Scrolling UserArea Example")
        self.AddSlider(self.ID_SLIDER, c4d.BFH_SCALEFIT | c4d.BFV_BOTTOM, 10)
        self.SetFloat(self.ID_SLIDER, 200, 1, 1000, 1)

        if self.ScrollGroupBegin(self.ID_SCROLLGRP, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, scrollflags=c4d.SCROLLGROUP_VERT):
            self.ua = ExampleUserArea(0,0,200,200)
            self.AddUserArea(self.ID_UA, c4d.BFH_LEFT | c4d.BFV_TOP)
            self.AttachUserArea(self.ua, self.ID_UA)
        self.GroupEnd()
        self.ua.Redraw()
        return True

def main():
    dlg = ExampleDialog()
    dlg.Open(c4d.DLG_TYPE_MODAL_RESIZEABLE, xpos=-2, ypos=-2, defaultw=400, defaulth=400)

if __name__=='__main__':
    main()

Hi @blastframe just as a temporary workaround (since I confirm there is a bug in our side also).

Here a solution that cancels the initials DrawMsg and replaces with a custom one (with the correct Size).
This way everything is working as expected (but keep in mind this is a hack, but I see only this solution as a workaround).

import c4d
import os
from c4d import gui

class ExampleUserArea(c4d.gui.GeUserArea):
    def __init__(self, x, y):
        super(ExampleUserArea, self).__init__()

        # Init the bitmap
        fn = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP)
        image = "random_img.jpg"
        path = os.path.join(fn,image)
        self.bmp = c4d.bitmaps.BaseBitmap()
        if self.bmp.InitWith(path)[0] != c4d.IMAGERESULT_OK:
            raise ValueError("Cannot load image " + path + ".")

        # During the init, set the width and height by the size of the Bitmap
        self.width, self.height = self.bmp.GetSize()

    def DrawMsg(self, x1, y1, x2, y2, msg):
        self.OffScreenOn()
        self.SetClippingRegion(x1, y1, x2, y2)
        self.DrawBitmap(self.bmp,
                        x1, y1, x2, y2,
                        0, 0, self.bmp.GetBw(), self.bmp.GetBh(),
                        c4d.BMP_NORMAL | c4d.BMP_ALLOWALPHA)

        self.DrawBorder(c4d.BORDER_IN, x1, y1, x2, y2)

    def GetMinSize(self):
        return self.width, self.height
    
    def Message(self, msg, result):
        # Catch the draw message to cancel it (return True)
        # and call ourself the DrawMsg with the dimension we expect
        if msg.GetId() == c4d.BFM_DRAW:
            self.DrawMsg(0, 0, self.width, self.height, c4d.BaseContainer())

            return True
    
        return c4d.gui.GeUserArea.Message(self, msg, result)

    def InputEvent(self, msg) :
        self.Redraw()
        return True

class ExampleDialog(c4d.gui.GeDialog):
    ID_SLIDER = 10000
    ID_UA = 10001
    ID_SCROLLGRP = 10002

    def __init__(self):
        pass

    def Command(self, id, data):
        if id == self.ID_SLIDER:
            self.ua.height = int(self.GetFloat(self.ID_SLIDER))
            self.ua.LayoutChanged()
            self.LayoutChanged(self.ID_SCROLLGRP)
        return True

    def CreateLayout(self):
        self.SetTitle("Scrolling UserArea Example")
        self.AddSlider(self.ID_SLIDER, c4d.BFH_SCALEFIT | c4d.BFV_BOTTOM, 10)
        self.SetFloat(self.ID_SLIDER, 200, 1, 1000, 1)

        if self.ScrollGroupBegin(self.ID_SCROLLGRP, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, scrollflags=c4d.SCROLLGROUP_VERT):
            self.ua = ExampleUserArea(0, 0)
            self.AddUserArea(self.ID_UA, c4d.BFH_LEFT | c4d.BFV_TOP)
            self.AttachUserArea(self.ua, self.ID_UA)

        self.GroupEnd()
        return True

def main():
    global dlg
    dlg = ExampleDialog()
    dlg.Open(c4d.DLG_TYPE_ASYNC, xpos=-2, ypos=-2, defaultw=400, defaulth=400)

if __name__=='__main__':
    main()

Cheers,
Maxime.

Hey @blastframe quickly looking at your code, you redraw the GeUserArea but not the whole group.

    def Command(self, id, data):
        if id == self.ID_SLIDER:
            self.ua.height = int(self.GetFloat(self.ID_SLIDER))
            print(self.ua.width,self.ua.height)
            self.ua.LayoutChanged()
            self.LayoutChanged(self.ID_SCROLLGRP)
        return True

Cheers,
Maxime

@m_adam Thanks for the reply, Maxime! That fixed the issue with the slider, however the issue remains when the group is scrolled. I tried this in the Command method, but it didn't work:

        if id == self.ID_SCROLLGRP:
            self.ua.LayoutChanged()
            self.LayoutChanged(self.ID_SCROLLGRP)

There doesn't seem to be any id or data passed to the Command method when the group is scrolled.

I also tried listening for the BFM_SCROLLGROUP_SCROLLED message:

        def Message(self, msg, result):
            if msg.GetId() == c4d.BFM_SCROLLGROUP_SCROLLED:
                self.ua.LayoutChanged()
                self.LayoutChanged(self.ID_SCROLLGRP)
            return c4d.gui.GeDialog.Message(self, msg, result)

This created a loop and froze Cinema 4D (I believe because self.LayoutChanged(self.ID_SCROLLGRP) may also be sending this Message?).

Can you please help me understand how to redraw the Scrollgroup & GeUserArea on scroll? Thank you!

Hi @blastframe, just to inform you I didn't forget you,

I thinks this may be another issue related to GeUserArea wrong coordinate topic:

However, I still needs some time to investigate from where it come.
I will keep you updated.
Cheers,
Maxime.

@m_adam Thank you very much for all your help, Maxime!

Yes, after you answered my question here, I used that workaround for the UserArea from this issue.

It is working, but I'd rather the safer solution you're seeking.

All the best.

Hi @blastframe just as a temporary workaround (since I confirm there is a bug in our side also).

Here a solution that cancels the initials DrawMsg and replaces with a custom one (with the correct Size).
This way everything is working as expected (but keep in mind this is a hack, but I see only this solution as a workaround).

import c4d
import os
from c4d import gui

class ExampleUserArea(c4d.gui.GeUserArea):
    def __init__(self, x, y):
        super(ExampleUserArea, self).__init__()

        # Init the bitmap
        fn = c4d.storage.GeGetC4DPath(c4d.C4D_PATH_DESKTOP)
        image = "random_img.jpg"
        path = os.path.join(fn,image)
        self.bmp = c4d.bitmaps.BaseBitmap()
        if self.bmp.InitWith(path)[0] != c4d.IMAGERESULT_OK:
            raise ValueError("Cannot load image " + path + ".")

        # During the init, set the width and height by the size of the Bitmap
        self.width, self.height = self.bmp.GetSize()

    def DrawMsg(self, x1, y1, x2, y2, msg):
        self.OffScreenOn()
        self.SetClippingRegion(x1, y1, x2, y2)
        self.DrawBitmap(self.bmp,
                        x1, y1, x2, y2,
                        0, 0, self.bmp.GetBw(), self.bmp.GetBh(),
                        c4d.BMP_NORMAL | c4d.BMP_ALLOWALPHA)

        self.DrawBorder(c4d.BORDER_IN, x1, y1, x2, y2)

    def GetMinSize(self):
        return self.width, self.height
    
    def Message(self, msg, result):
        # Catch the draw message to cancel it (return True)
        # and call ourself the DrawMsg with the dimension we expect
        if msg.GetId() == c4d.BFM_DRAW:
            self.DrawMsg(0, 0, self.width, self.height, c4d.BaseContainer())

            return True
    
        return c4d.gui.GeUserArea.Message(self, msg, result)

    def InputEvent(self, msg) :
        self.Redraw()
        return True

class ExampleDialog(c4d.gui.GeDialog):
    ID_SLIDER = 10000
    ID_UA = 10001
    ID_SCROLLGRP = 10002

    def __init__(self):
        pass

    def Command(self, id, data):
        if id == self.ID_SLIDER:
            self.ua.height = int(self.GetFloat(self.ID_SLIDER))
            self.ua.LayoutChanged()
            self.LayoutChanged(self.ID_SCROLLGRP)
        return True

    def CreateLayout(self):
        self.SetTitle("Scrolling UserArea Example")
        self.AddSlider(self.ID_SLIDER, c4d.BFH_SCALEFIT | c4d.BFV_BOTTOM, 10)
        self.SetFloat(self.ID_SLIDER, 200, 1, 1000, 1)

        if self.ScrollGroupBegin(self.ID_SCROLLGRP, c4d.BFH_SCALEFIT | c4d.BFV_SCALEFIT, scrollflags=c4d.SCROLLGROUP_VERT):
            self.ua = ExampleUserArea(0, 0)
            self.AddUserArea(self.ID_UA, c4d.BFH_LEFT | c4d.BFV_TOP)
            self.AttachUserArea(self.ua, self.ID_UA)

        self.GroupEnd()
        return True

def main():
    global dlg
    dlg = ExampleDialog()
    dlg.Open(c4d.DLG_TYPE_ASYNC, xpos=-2, ypos=-2, defaultw=400, defaulth=400)

if __name__=='__main__':
    main()

Cheers,
Maxime.

@m_adam Thank you, Maxime, for going to this effort.