Ruby Proc returns 在 Ruby Tkinter 中所有按钮点击的相同值

Ruby Proc returns the same value for all button clicks in Ruby Tkinter

我正在使用 Ruby Tkinter 开发一个应用程序,我 运行 遇到了一个为循环中的按钮定义事件的问题。

以下面的代码为例:

array = ["one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve"]
new_array = []

for i in 0..(array.length - 1)
    new_array.append [i,Proc.new {puts array[i]}]
end

for i in 0..(new_array.length - 1)
    new_array[i][1].call
end

当上面的代码是 运行 时,我按预期得到以下输出:

one
two
three
four
five
six
seven
eight
nine
ten
eleven
twelve

但是如果我想为我的 Tkinter 应用程序循环创建一组按钮,应用相同的概念:

require 'tk'
root = TkRoot.new()
root.title("Test")

list = ["one","two","three","four","five","six","seven","eight","nine","ten","eleven","twelve"]

for i in (0..list.length - 1)
    button = Tk::Tile::Button.new(root).pack :side => "top", :expand => false, :fill => "x"
    button.text = (i + 1).to_s
    button.command = Proc.new {puts list[i]}
end

Tk.mainloop

如果我按下上面代码中 window 中的所有按钮,我会得到这个输出:

twelve
twelve
twelve
twelve
twelve
twelve
twelve
twelve
twelve
twelve
twelve
twelve

这是怎么回事?为什么我的按钮事件都是一样的? 我在 Python 中看到过关于 Tk 的“后期事件绑定”问题,但我没能找到很多解决方案。特别是 Ruby.

这是一个常见的闭包问题:当 Proc.new 被调用时,过程最终引用 i 本身而不是它的值。这在 JavaScript 中比 Ruby 中更常见,因为在 Ruby.

中很少使用循环

最简单的 Ruby 解决方案是根本不使用循环:

list.each_with_index do |item, i|
  button         = Tk::Tile::Button.new(root).pack side: 'top', expand: false, fill: 'x'
  button.text    = (i + 1).to_s
  button.command = Proc.new { puts item }
end

你也可以说:

list.length.times do |i|
  button         = Tk::Tile::Button.new(root).pack side: 'top', expand: false, fill: 'x'
  button.text    = (i + 1).to_s
  button.command = Proc.new { puts list[i] }
end

但是 each_with_index 更加地道。