Wiele okien w Ruby+Qt

Hej, używam qtbindings, ale nie potrafię przekazać wartości ustawionych w jednym oknie do drugiego okna.
W przykładowym kodzie w SubWindow ustawiam spin1 oraz spin2 i chce ich wartości odczytać w MainWindow.
Co zle robie?

[code]#!/usr/bin/env ruby

coding: utf-8

require ‘rubygems’
require ‘Qt’

module MyApp
class MainWindow < Qt::MainWindow
slots ‘subwindow()’

def initialize(parent = nil)
  super
  button = Qt::PushButton.new("Okno")
  connect(button, SIGNAL('clicked()'), self, SLOT('subwindow()'))
  @label = Qt::Label.new("")
  widget = Qt::Widget.new
  setCentralWidget(widget)
  widget.layout = Qt::VBoxLayout.new do |l|
    l.addWidget(@label)
    l.addWidget(button)
  end
end

def subwindow()
  arr = {:spin1=>0, :spin2=>0}
  window = SubWindow.new
  window.windowModality = Qt::ApplicationModal
  window.show
  @label.setText("Spin1: #{arr[:spin1]} | Spin2: #{arr[:spin2]}")
end

end

class SubWindow < Qt::Widget
slots ‘accept()’, ‘reject()’
def initialize(parent = nil)
super(parent)
ok = Qt::PushButton.new(“OK”)
cancel = Qt::PushButton.new(“Cancel”)
connect(ok, SIGNAL(‘clicked()’), self, SLOT(‘accept()’))
connect(cancel, SIGNAL(‘clicked()’), self, SLOT(‘reject()’))
@spin1 = Qt::SpinBox.new { |s| s.range = 0…99 }
@spin2 = Qt::SpinBox.new { |s| s.range = 100…199 }
self.layout = Qt::GridLayout.new do |l|
l.addWidget(Qt::Label.new("SpinBox 1: "), 0, 0)
l.addWidget(@spin1, 0, 1)
l.addWidget(Qt::Label.new("SpinBox 2: "), 1, 0)
l.addWidget(@spin2, 1, 1)
l.addWidget(ok, 2, 0)
l.addWidget(cancel, 2, 1)
end
end

def accept()
  arr = {}
  arr[:spin1] = @spin1.value
  arr[:spin2] = @spin2.value
  # TODO Jak przekazać te wartości do okna głównego?
  puts arr
  close()
end

def reject()
  close()
end

end

myApp = Qt::Application.new(ARGV)
mainWin = MainWindow.new
mainWin.show
myApp.exec
end[/code]

Nie znam się na Qt bindingach w Ruby, ale tak na chłopski rozum to bym utworzył jakiś model do przechowywania danych, do którego miałby dostęp zarówno widok główny jak i podwidok.

[code=ruby]#!/usr/bin/env ruby

coding: utf-8

require ‘rubygems’
require ‘Qt’

module MyApp
class Settings
attr_reader :setting_one, :setting_two
end

class MainWindow < Qt::MainWindow
slots ‘subwindow()’

def initialize(parent = nil)
  super
  button = Qt::PushButton.new("Okno")
  connect(button, SIGNAL('clicked()'), self, SLOT('subwindow()'))
  @label = Qt::Label.new("")
  widget = Qt::Widget.new
  setCentralWidget(widget)
  widget.layout = Qt::VBoxLayout.new do |l|
    l.addWidget(@label)
    l.addWidget(button)
  end
end

def subwindow()
  model = Settings.new
  window = SubWindow.new model
  window.windowModality = Qt::ApplicationModal
  window.show
  @label.setText("Spin1: #{model.setting_one} | Spin2: #{model.setting_two}")
end

end

class SubWindow < Qt::Widget
slots ‘accept()’, ‘reject()’
attr_reader :model
def initialize(model = Settings.new, parent = nil)
super(parent)
self.model = model
ok = Qt::PushButton.new(“OK”)
cancel = Qt::PushButton.new(“Cancel”)
connect(ok, SIGNAL(‘clicked()’), self, SLOT(‘accept()’))
connect(cancel, SIGNAL(‘clicked()’), self, SLOT(‘reject()’))
@spin1 = Qt::SpinBox.new { |s| s.range = 0…99 }
@spin2 = Qt::SpinBox.new { |s| s.range = 100…199 }
self.layout = Qt::GridLayout.new do |l|
l.addWidget(Qt::Label.new("SpinBox 1: "), 0, 0)
l.addWidget(@spin1, 0, 1)
l.addWidget(Qt::Label.new("SpinBox 2: "), 1, 0)
l.addWidget(@spin2, 1, 1)
l.addWidget(ok, 2, 0)
l.addWidget(cancel, 2, 1)
end
end

def accept()
  self.model.setting_one = @spin1.value
  self.model.setting_two = @spin2.value
  close()
end

def reject()
  close()
end

end

myApp = Qt::Application.new(ARGV)
mainWin = MainWindow.new
mainWin.show
myApp.exec
end[/code]
Nie gwarantuję, że działa. Nie mam Qt zainstalowanego.
Model przekazujesz po prostu do konstruktora widoku.

PS. Mam nadzieję, że to tylko zabawa, a jak będziesz pisał aplikację na serio, to spin1 będzie miał więcej mówiącą nazwę.

[quote=sevos]Nie gwarantuję, że działa. Nie mam Qt zainstalowanego.
Model przekazujesz po prostu do konstruktora widoku.

PS. Mam nadzieję, że to tylko zabawa, a jak będziesz pisał aplikację na serio, to spin1 będzie miał więcej mówiącą nazwę.[/quote]
Problem w tym, że model nie dostaje danych “na czas”, czyli po fragmencie z metody subwindow():

window.windowModality = Qt::ApplicationModal window.show
w modelu nie ma tych danych. Ponieważ zaraz po wykonaniu show wykonuje:

  @label.setText("Spin1: #{model.setting_one} | Spin2: #{model.setting_two}")

W tym czasie subwindow jeszcze nie zostało zamknięte (nie kliknięto OK), a więc nie mogło wczytać danych do modelu. Nie wiem czy powinienem jakiś sygnał wysłać? Może w pętli sprawdzać czy są już nowe dane w modelu?

PS Tak, nazwy spin1, spin2 są tylko przykładowe.

Model powinien być obserwowany przez okna. Użyj tego wzorca:

Musisz przesłonić settery:

def setting_one=(val) super changed and notify_observers(Time.now, self) end
w ogóle to możesz zainkludować taki moduł (oczywiście moduł observable też potrzebny!)

[code=ruby]module NotifyingSetters
module ClassMethods
def notify_on_change(*fields)
fields.each do |field|
previous_method = instance_method :"#{field}="
define_method :"#{field}=" do |val|
result = previous_method.bind(self).call(val)
self.changed and self.notify_observers(Time.now, self)
return result
end
end
end
end

def self.included(base)
base.send(:extend, ClassMethods)
end
end[/code]
Również nie wiem, czy działa, ogólnie, pomysł :wink:

Jest bardzo ładny moduł w Ruby do tego:

Pomysł mi się podoba. Zrobiłem coś podobnego i działa. Temat do zamknięcia. :slight_smile: