当文件加载到我的GJS应用程序中时,我如何发送和接收信号



我有一个应用程序,需要打开一个文件并相应地更新UI元素。我可以选择并打开文件(并记录文件内容),但我不能告诉UI元素更新。

我试着读到,我可以创建几乎任何对象并向其添加信号,但我需要从导入库中的函数中发出信号。

我正试着这样做:(在我从磁盘读取文件的功能中)

try {
Signals.addSignalMethods(this);
this.emit('update_ui', true);
} catch(e) {
print(e);
}

(在主应用程序类中)

Signals.addSignalMethods(this);
this.connect('update_ui',() => {
try {
print('>>> updating UI');
this.ui.updateUI();
} catch (e) {
print(e);
}
});

我在运行应用程序时没有遇到任何错误,但从未调用过更新功能。我怎样才能让信号通过?

以下是main.js文件中应该捕获信号的代码:

#!/usr/bin/gjs

Gio = imports.gi.Gio;
GLib = imports.gi.GLib;
Gtk = imports.gi.Gtk;
Lang = imports.lang;
Webkit = imports.gi.WebKit2;
Signals = imports.signals;
GObject = imports.gi.GObject;
Pango = imports.gi.Pango;
//
// add app folder to path
//
function getAppFileInfo() {
let stack = (new Error()).stack,
stackLine = stack.split('n')[1],
coincidence, path, file;
if (!stackLine) throw new Error('Could not find current file (1)');
coincidence = new RegExp('@(.+):\d+').exec(stackLine);
if (!coincidence) throw new Error('Could not find current file (2)');
path = coincidence[1];
file = Gio.File.new_for_path(path);
return [file.get_path(), file.get_parent().get_path(), file.get_basename()];
}
const path = getAppFileInfo()[1];
imports.searchPath.push(path);
const myApp = new Lang.Class({
Name: 'My Application',
// Create the application itself
_init: function() {
this.application = new Gtk.Application();
// Connect 'activate' and 'startup' signals to the callback functions
this.application.connect('activate', Lang.bind(this, this._onActivate));
this.application.connect('startup', Lang.bind(this, this._onStartup));
},
// Callback function for 'activate' signal presents windows when active
_onActivate: function() {
this._window.present();
},
// Callback function for 'startup' signal builds the UI
_onStartup: function() {
this._buildUI();
},
// Build the application's UI
_buildUI: function() {
// Create the application window
this._window = new Gtk.ApplicationWindow({
application: this.application,
title: "My App",
default_height: 200,
default_width: 400,
window_position: Gtk.WindowPosition.CENTER
});
//
// menu bar
//
const Menubar = imports.lib.menubar;
this._window.set_titlebar(Menubar.getHeader());
Signals.addSignalMethods(this);
this.connect('update_ui', () => {
try {
print('>>> updating UI');
//this.ui.updateUI();
} catch (e) {
print(e);
}
});
// Vbox to hold the switcher and stack.
this._Vbox = new Gtk.VBox({
spacing: 6
});
this._Hbox = new Gtk.HBox({
spacing: 6,
homogeneous: true
});
// const UI = imports.UI.UI;
// this.ui = new UI.UIstack();
// this.ui._buildStack();
// this._Hbox.pack_start(this.ui._stack_switcher, true, true, 0);
this._Vbox.pack_start(this._Hbox, false, false, 0);
// this._Vbox.pack_start(this.ui._Stack, true, true, 0);
// Show the vbox widget
this._window.add(this._Vbox);
// Show the window and all child widgets
this._window.show_all();
},
});
// Run the application
const app = new myApp();
app.application.run(ARGV);

这是发出信号的标题栏文件:

const GLib = imports.gi.GLib;
const Gtk = imports.gi.Gtk;
const File = imports.lib.file;
const PopWidget = function(properties) {
let label = new Gtk.Label({
label: properties.label
});
let image = new Gtk.Image({
icon_name: 'pan-down-symbolic',
icon_size: Gtk.IconSize.SMALL_TOOLBAR
});
let widget = new Gtk.Grid();
widget.attach(label, 0, 0, 1, 1);
widget.attach(image, 1, 0, 1, 1);
this.pop = new Gtk.Popover();
this.button = new Gtk.ToggleButton();
this.button.add(widget);
this.button.connect('clicked', () => {
if (this.button.get_active()) {
this.pop.show_all();
}
});
this.pop.connect('closed', () => {
if (this.button.get_active()) {
this.button.set_active(false);
}
});
this.pop.set_relative_to(this.button);
this.pop.set_size_request(-1, -1);
this.pop.set_border_width(8);
this.pop.add(properties.widget);
};
const getHeader = function() {
let headerBar, headerStart, imageNew, buttonNew, popMenu, imageMenu, buttonMenu;
headerBar = new Gtk.HeaderBar();
headerBar.set_title("My App");
headerBar.set_subtitle("Some subtitle text here");
headerBar.set_show_close_button(true);
headerStart = new Gtk.Grid({
column_spacing: headerBar.spacing
});
// this.widgetOpen = new PopWidget({ label: "Open", widget: this.getPopOpen() });
imageNew = new Gtk.Image({
icon_name: 'document-open-symbolic',
icon_size: Gtk.IconSize.SMALL_TOOLBAR
});
buttonNew = new Gtk.Button({
image: imageNew
});
buttonNew.connect('clicked', () => {
const opener = new Gtk.FileChooserDialog({
title: 'Select a file'
});
opener.set_action(Gtk.FileChooserAction.OPEN);
opener.add_button('open', Gtk.ResponseType.ACCEPT);
opener.add_button('cancel', Gtk.ResponseType.CANCEL);
const res = opener.run();
if (res == Gtk.ResponseType.ACCEPT) {
const filename = opener.get_filename();
print(filename);
const fileData = File.open(filename);
print(JSON.stringify(fileData, null, 2));
File.unRoll(fileData);
// 
// SHOULD SEND A SIGNAL
//
try {
Signals.addSignalMethods(this);
this.emit('update_ui', true);
} catch (e) {
print(e);
}
// this._window.ui.updateUI();
}
opener.destroy();
});
// headerStart.attach(this.widgetOpen.button, 0, 0, 1, 1);
headerStart.attach(buttonNew, 1, 0, 1, 1);
headerBar.pack_start(headerStart);
popMenu = new Gtk.Popover();
imageMenu = new Gtk.Image({
icon_name: 'document-save-symbolic',
icon_size: Gtk.IconSize.SMALL_TOOLBAR
});
buttonMenu = new Gtk.MenuButton({
image: imageMenu
});
buttonMenu.set_popover(popMenu);
popMenu.set_size_request(-1, -1);
buttonMenu.set_menu_model(this.getMenu());
headerBar.pack_end(buttonMenu);
return headerBar;
};
const getPopOpen = function() {
/* Widget popover */
let widget = new Gtk.Grid(),
label = new Gtk.Label({
label: "Label 1"
}),
button = new Gtk.Button({
label: "Other Documents ..."
});
button.connect('clicked', () => {
this.widgetOpen.pop.hide();
this.printText('Open other documents');
});
button.set_size_request(200, -1);
widget.attach(label, 0, 0, 1, 1);
widget.attach(button, 0, 1, 1, 1);
widget.set_halign(Gtk.Align.CENTER);
return widget;
};
const getMenu = function() {
/* GMenu popover */
let menu, section, submenu;
menu = new Gio.Menu();
section = new Gio.Menu();
section.append("Save As...", 'app.saveAs');
section.append("Save All", 'app.saveAll');
menu.append_section(null, section);
section = new Gio.Menu();
submenu = new Gio.Menu();
section.append_submenu('View', submenu);
submenu.append("View something", 'app.toggle');
submenu = new Gio.Menu();
section.append_submenu('Select', submenu);
submenu.append("Selection 1", 'app.select::one');
submenu.append("Selection 2", 'app.select::two');
submenu.append("Selection 3", 'app.select::thr');
menu.append_section(null, section);
section = new Gio.Menu();
section.append("Close All", 'app.close1');
section.append("Close", 'app.close2');
menu.append_section(null, section);
// Set menu actions
let actionSaveAs = new Gio.SimpleAction({
name: 'saveAs'
});
actionSaveAs.connect('activate', () => {
const saver = new Gtk.FileChooserDialog({
title: 'Select a destination'
});
saver.set_action(Gtk.FileChooserAction.SAVE);
saver.add_button('save', Gtk.ResponseType.ACCEPT);
saver.add_button('cancel', Gtk.ResponseType.CANCEL);
const res = saver.run();
if (res == Gtk.ResponseType.ACCEPT) {
const filename = saver.get_filename();
print(filename);
const data = File.rollUp();
File.save(filename, data);
// let data = JSON.stringify(<FILE DATA>, null, 't');
// GLib.file_set_contents(filename, data);
}
saver.destroy();
});
APP.add_action(actionSaveAs);
let actionSaveAll = new Gio.SimpleAction({
name: 'saveAll'
});
actionSaveAll.connect('activate', () => {
Gtk.FileChooserAction.OPEN
});
APP.add_action(actionSaveAll);
let actionClose1 = new Gio.SimpleAction({
name: 'close1'
});
actionClose1.connect('activate', () => {
this.printText('Action close all');
});
APP.add_action(actionClose1);
let actionClose2 = new Gio.SimpleAction({
name: 'close2'
});
actionClose2.connect('activate', () => {
this.printText('Action close');
});
APP.add_action(actionClose2);
let actionToggle = new Gio.SimpleAction({
name: 'toggle',
state: new GLib.Variant('b', true)
});
actionToggle.connect('activate', (action) => {
let state = action.get_state().get_boolean();
if (state) {
action.set_state(new GLib.Variant('b', false));
} else {
action.set_state(new GLib.Variant('b', true));
}
this.printText('View ' + state);
});
APP.add_action(actionToggle);
let variant = new GLib.Variant('s', 'one');
let actionSelect = new Gio.SimpleAction({
name: 'select',
state: variant,
parameter_type: variant.get_type()
});
actionSelect.connect('activate', (action, parameter) => {
let str = parameter.get_string()[0];
if (str === 'one') {
action.set_state(new GLib.Variant('s', 'one'));
}
if (str === 'two') {
action.set_state(new GLib.Variant('s', 'two'));
}
if (str === 'thr') {
action.set_state(new GLib.Variant('s', 'thr'));
}
this.printText('Selection ' + str);
});
APP.add_action(actionSelect);
return menu;
};

它不是一个类,只是一个静态库。。。它是由main.js文件导入的,所以我认为范围是一样的,但可能不是。。。

当我再次回答你的问题时,我会过度回答,因为我看到了一些困难和一些你可以使用的"最佳实践"。我认为使用GtkApplication的直接子类和ES6类更可取。

const Gio = imports.gi.Gio;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
var MyApp = GObject.registerClass({
// This must be unique, although I don't believe you have to specify it
GTypeName: 'MyApp',
// GObject already have their own signal system, so invoking addSignalMethods()
// will override the existing methods and could cause you problems. Here's a
// simple example of how you'd add a GObject signal to a subclass; the resulting
// callback would look like:
//
//     myapp.connect('update-ui', (application, bool) => { ... });
Signals: {
'update-ui': {
param_types: [GObject.TYPE_BOOLEAN]
},
}
}, class MyApp extends Gtk.Application {
_init(params) {
// You could pass the regular params straight through
//super._init(params);
// Or setup your class to be initted without having to pass any args in your
// constructor
super._init({
// This will also become your well-known name on DBus, with the matching
// object path of /org/github/username/MyApp.
//
// GApplication framework will also use these for other things like any
// GResource you bundle with your application.
application_id: 'com.github.username.MyApp',
// There are other flags you can set to allow your app to be a target for
// "Open with...", but that's not what you're asking about today :)
flags: Gio.ApplicationFlags.HANDLES_OPEN
});
// You can also do other setup , however it's important to note that
// GApplication's are generally "single-instance" processes, so you want most
// of that setup in ::startup which will only run if this is the primary
// instance.
//
// On the other hand if you pass HANDLES_OPEN, for example, that signal/vfunc
// will still be invoked.
GLib.set_prgname(this.application_id);
GLib.set_application_name('MyApp');
}
_buildUI() {
// ...
}
// defining a virtual function usually means overriding the default handler for a
// a signal. In this case, it's not much of an issue but for frequently emitted
// signals it avoids "marshalling" the C values into JS values and back again.
//
// In other words, defining this function means it will be called as if  you
// connected to the ::activate signal.
vfunc_activate() {
this._window.present();
}
// ::activate is a special case, but for most vfunc's is necessary to "chain up",
// which really just means calling the super class's original function inside your
// override.
vfunc_startup() {
// In ::startup we need to chain up first since GApplication does important
// setup checks here
super.vfunc_startup();
// your stuff after
this._buildUI();
// In _buildUI() you correctly passed your GtkApplication as the application
// property, which will keep the application running so long as that window
// is open.
//
// If you wanted your application to stay open regardless, you call hold()
this.hold();
}
vfunc_shutdown() {
// ::shutdown on the other hand is the reverse; first we do our stuff, then
// chain up to super class. This is a good time to do any cleanup you need
// before the process exits.
this._destroyUI();
// chain up
super.vfunc_shutdown();
}
});
// For technical reasons, this is the proper way you should start your application
(new MyApp()).run([imports.system.programInvocationName].concat(ARGV));

addSignalMethods()函数只需要调用一次,但如上所述,它将覆盖现有的信号方法,如果在已经定义了信号的GObject上调用它,可能会导致问题。它基本上是这样做的:

addSignalMethods(obj) {
obj.emit = function() {
// custom signal code
}
...

它实际上是为那些还没有信号系统的纯JS类设计的。

// SHOULD SEND A SIGNAL
//
try {
Signals.addSignalMethods(this);
this.emit('update_ui', true);
} catch (e) {
print(e);
}

这对您不起作用的原因是getHeaderbar()imports.lib.menubar中的顶级函数,所以当您调用addSignalMethods(this)时,this === imports.lib.menubar。因此,为了捕捉到这个信号,你必须拨打:

imports.lib.menubar.connect('update_ui', () => {});

当你在_buildUI():中调用它时

Signals.addSignalMethods(this);
this.connect('update_ui', () => {
try {
print('>>> updating UI');
//this.ui.updateUI();
} catch (e) {
print(e);
}
});

this === MyApp,所以您首先要覆盖MyApp的信号方法,然后连接到它本身。Signals导入相当宽松,因此在使用前不需要定义信号;你只需要连接到你想要的东西,然后发出你想要的。这就是为什么你没有收到任何错误或警告。

有几种方法可以解决您的问题:

// (1) Pick an object to emit and connect from and only add the signal methods once
Signals.addSignalMethods(headerBar);
// Since you're using an arrow function you can call this in the same place since it
// should still be in scope
headerBar.emit('update_ui', true);
// You can connect by grabbing the headerbar widget from your constructed window
this._window.get_titlebar().connect('update_ui', (headerBar, bool) => {});
// (2) Define a signal *on* your GtkApplication and use it as a relay
// Connect from a function in MyApp (so in `this.connect`, `this === MyApp`)
// To set `this` for the callback itself, use `Function.bind()`
this.connect('update-ui', this.ui.updateUI.bind(ui));
// You can always grab the primary instance of your GtkApplication (from anywhere)
// and emit the signal to invoke the MyApp.ui.updateUI() function
let myApp = Gio.Application.get_default();
myApp.emit('update-ui', true);
// (3) How I'd probably do it; just invoke the method directly
let myApp = Gio.Application.get_default();
myApp.ui.updateUI();

相关内容

  • 没有找到相关文章

最新更新