From: jfn@... (Jeremy Nelson) Date: 2001-01-27T09:40:08+09:00 Subject: [ruby-talk:9968] [SOLUTION] Re: Ruby/Tk -- Creating a class with Labels and Buttons that talk to each other Sorry to follow up on my own post, but a workable solution has been arrived at and it is general purpose, and it seems to work, and so I'd like to share it with the world becuase I can't be the only one who has ever had this come up! =) This explanation is kind of geared towards ruby newbies, so you advanced users please bear with me. =) What I had tried to do was create a class that created several different Tk widgets, and created a "button box", where the totality of the whole thing was an easy way for a user to input a number to the application. When the user presses a button, that button (remember, there is a plurality of them) needs to be able to "talk to" a Tk label widget which will show the number on that button. This way the user can see what buttons they pushed. The problem was how to get an arbitrary number of buttons to talk to a single widget. I was trying to solve this problem with TkVariables, and that was fine, but I could not figure out how to create a TkVariable in each instantiation of a class and then refer to that TkVariable in the callbacks to the various TkButtons. The solution to this is to convert the instance variable to another type (ie, a local variable). Because all variable names in ruby are actually references to some underlying object, it does not matter exactly what type of variable name you choose to refer to an object; the only thing that matters is whether or not that variable name is in scope (visible) in the place where you want to use it. Thus, the solution to this problem is the following code: class Grid # 1 def localvar= (x) # 2 @localvar.value = x.to_s # 3 end # 4 def initialize (parent, rows, columns) # 5 @localvar = TkVariable.new() # 6 @localvar.value = "" # 7 foo = @localvar # 8 TkLabel.new(parent) { textvariable foo }.pack # 9 @frame = TkFrame.new(parent).pack # 10 argh = self.method("localvar=") # 11 rows.times { |i| # 12 columns.times { |j| # 13 currval = (i * rows + j).to_s # 14 TkButton.new(@frame) { # 15 command proc { # 16 argh.call(self.text) # 17 } # 18 text currval # 19 grid ( 'row' => i, 'column' => j) # 20 } # 21 } # 22 } # 23 end # 24 end # 25 mw = TkRoot.new # 26 Grid.new(mw, 5,5) # 27 Grid.new(mw, 6,6) # 28 Tk.mainloop # 29 [explanation] On line 6, I create the TkVariable that we will be using to coordinate the "talking" between each of the TkButtons and the TkLabel. Ruby's rules do not permit @localvar to be in scope in the option block to TkLabel.new, because it is an instance variable in another class (as far as I understand). However, you can take advantage of local variables always being in scope in the current function and create a reference to @localvar to a local variable in line #8 -- now you can use 'foo' as the argument to 'textvariable' in the TkLabel! The other problem is what the buttons will do when they are pressed. They need to call a method, but they do not have any way to identify with the object that they 'belong' to. So in line 11, we create a local variable that points to 'self.localvar=', and since that is a local variable, it will be in scope in any option lists to Tk widgets!. So in line 17, in the Tk callback for each button, we tell it to "call" the method that we have created a local variable reference to in line 11. This permits each button to know not only what function to call, but also which object to call that function! The function that sets the value of our TkVariable is on lines 2, 3, and 4. It basically just calls the "value=" method for the TkVariable object that we have. And since we have a name for that TkVariable visible throughout the class (@localvar) we can use it. Thanks for bearing with me! I'm excited! This is neat! I've used entirely too many bangs in this posting! ;-) Jeremy