Right way to set and get variables (with refresh) while writing Classes?

I’m trying to understand how to correctly write a Class that can accept and return values. e.g.

a = MyClass;
a.backgroundColor; // get Color
a.backgroundColor_(Color.red); // set new Color and instantly apply 

And stuck on this stage:

MyClass {
	var <>backgroundColor, <>stringColor;
	classvar window, text;

	*new { |backgroundColor, stringColor|

		super.newCopyArgs(backgroundColor, stringColor).init;

	}

	init {

		backgroundColor = backgroundColor ? Color.white;
		stringColor = stringColor ? Color.black;
		this.window(backgroundColor);
		this.text(stringColor);

	}

	window { |backgroundColor|

		window = Window("", Rect(100, 500, 100, 100), false)
		.background_(backgroundColor)
		.front;

	}

	text { |stringColor|

		text = StaticText(window, window.view.bounds)
		.align_(\center)
		.string_("TEST")
		.stringColor_(stringColor);

	}

}

In this case I can’t get a value via a.backgroundColor. Since Color been stored inside instance variable after initialisation it should be accessible via so-called method … or not? It would be helpful if someone can lead me to figure out the right logic to build such classes. Thank you

1 Like
MyClass {
    var <>backgroundColor, <>stringColor; 
    classvar window, text;

    *new { arg backgroundColor = Color.white, stringColor = Color.black;
        ^super.newCopyArgs(backgroundColor, stringColor).refresh;

    }

    refresh {
        this.window(this.backgroundColor);
        this.text(this.stringColor);
    }

    window { |backgroundColor|
        window = Window("", Rect(100, 500, 100, 100), false)
            .background_(backgroundColor)
            .front;
    }

    text { |stringColor|
        text = StaticText(window, window.view.bounds)
            .align_(\center)
            .string_("TEST")
            .stringColor_(stringColor);
    }
}

a = MyClass.new; 
a.backgroundColor; 
a.backgroundColor_(Color.red); 
a.refresh; 

The init method (maybe call it refresh?) is checking if backgroundColor and stringColor are nil and then setting them to defaults. It’s not really a init method, just set the default values for backgroundColor and stringColor in the arguments of the *new method. Then, also remove the check ?? from it, since it is not necessary.

Why do you want window and text as classvar? Maybe you don’t want that, unless you’re planning a very specific context when creating more instances of your class.

Also, sometimes it’s good to use another name to avoid shadowing the names. For example, instead of blackground, you can call a local argument or variable with a different name, since they are not the same object, for example. It avoids confusion.

Other ideas:

  • Implement methods to update the window and text properties dynamically. For instance, if you change the backgroundColor, the window’s background could automatically update to reflect this change.
  • Add validation for the input parameters. For instance, check if the backgroundColor and stringColor are valid Color objects.
1 Like

This is good way to deal with. Thanks for tips

You’re right. That was some unintentional typo

I tried another approach. What do you think of this one:

MyClass {
	var <>currentBackgroundColor, <>window, <>text;

	*new { |backgroundColor|

		^super.new.init(backgroundColor ? Color.black)

	}

	init { |backgroundColor|

		window = Window("", Rect(100, 500, 100, 100), false)
		.background_(this.backgroundColor(backgroundColor))
		.front;

		text = StaticText(window, window.view.bounds)
		.align_(\center)
		.string_("TEST")
		.stringColor_(Color.white)

	}

	backgroundColor { |backgroundColor|

		^if (backgroundColor.isNil, {
			currentBackgroundColor
		}, {
			currentBackgroundColor = backgroundColor
		})

	}

	backgroundColor_ { |backgroundColor|

		window.background = backgroundColor

	}

}

Seems like values can be accessed and updated with instant ‘refresh’ now.

a = MyClass(Color.red);
a.backgroundColor;
a.backgroundColor = Color.blue;

Ok, trying to describe what changes. The code I sent organized your snippet, your code actually improved somethings, but made other things a bit confusing if your code becomes a large project. I think you could try one more version that combines organization and straightforwardness.

The code I sent is more adaptable. It lets you separately manage creating the user interface and changing settings. Your is sort of more straightforward, especially when you want to set the initial background color quickly. But you could do it differently.

For updating things on the fly, the first method clearly uses a refresh method. The second method can change the background color as needed, but it’s not as obvious how it handles changes to other settings.

What would be better? To “refresh” any element your change, not just this one.

When it comes to ease of understanding and keeping things organized, the first does a better job by keeping different tasks separate. This is really helpful for larger projects. The second method takes up less space, but it might get tricky to handle if you keep adding more features.

And again: avoid using the same name for different things, it’s confusing and it will shadow your objects.

1 Like

I tried another way using .newCopyArgs. Now it’s possible to initialise values, instantly get/set values and update/refresh values separately as well. I’d say that it’s also easier to read the code now and follow its logic.

MyClass {
	var <>currentBackgroundColor, <>currentTextColor, <>window, <>text;

	*new { |backgroundColor, textColor|

			^super.newCopyArgs(backgroundColor, textColor).init 
		
	}

	init {

		this.buildWindow;
		this.buildText;

	}

	buildWindow {

		window = Window("", Rect(100, 500, 100, 100), false)
		.background_(currentBackgroundColor)
		.front;

	}

	buildText {

		text = StaticText(window, window.view.bounds)
		.align_(\center)
		.string_("TEST")
		.stringColor_(currentTextColor);

	}

	backgroundColor {

		^currentBackgroundColor

	}

	backgroundColor_ { |backgroundColor|

		window.background = backgroundColor

	}

	refresh {

		window.background_(currentBackgroundColor);

	}

}
a = MyClass(Color.black, Color.yellow);
a.currentBackgroundColor = Color.clear;
a.refresh;
a.backgroundColor = Color.blue;
a.text.string = "TEST".scramble;

One thing about newCopyArgs that can be a gotcha is - order matters! I usually comment those area in my code to flag to my older self / others that changing order or adding args can break code

/*
Josh Parmenter
www.realizedsound.net/josh
*/

2 Likes