0

下面是 Genie 中 ToolbarButton 的工作代码。目标是获取所选文件的 uri 并将其返回给类的构造/初始化。问题是在我遇到的所有示例中都使用了全局 _variables(如下面的代码所示)。它看起来不直观,我担心每当代码变大时,消除错误就会变得更加困难,因为这些变量会开始累积。是否有任何其他方法可以使函数 openfile 将 uri 返回到类的构造/初始化中的常规变量?

这是代码:

uses
    Granite.Widgets
    Gtk

init
    Gtk.init (ref args)

    var app = new Application ()
    app.show_all ()
    Gtk.main ()

// This class holds all the elements from the GUI
class Application : Gtk.Window

    _view:Gtk.TextView
    _uri:string

    construct ()

        // Prepare Gtk.Window:
        this.window_position = Gtk.WindowPosition.CENTER
        this.destroy.connect (Gtk.main_quit)
        this.set_default_size (400, 400)


        // Headerbar definition
        headerbar:Gtk.HeaderBar = new Gtk.HeaderBar()
        headerbar.show_close_button = true
        headerbar.set_title("My text editor")

        // Headerbar buttons
        open_button:Gtk.ToolButton = new ToolButton.from_stock(Stock.OPEN)
        open_button.clicked.connect (openfile)

        // Add everything to the toolbar
        headerbar.pack_start (open_button)
        show_all ()
        this.set_titlebar(headerbar)

        // Box:
        box:Gtk.Box = new Gtk.Box (Gtk.Orientation.VERTICAL, 1)
        this.add (box)

        // A ScrolledWindow:
        scrolled:Gtk.ScrolledWindow = new Gtk.ScrolledWindow (null, null)
        box.pack_start (scrolled, true, true, 0)

        // The TextView:
        _view = new Gtk.TextView ()
        _view.set_wrap_mode (Gtk.WrapMode.WORD)
        _view.buffer.text = "Lorem Ipsum"
        scrolled.add (_view)

    def openfile (self:ToolButton)

        var dialog = new FileChooserDialog ("Open file",
                                        this,
                                        FileChooserAction.OPEN,
                                        Stock.OK,     ResponseType.ACCEPT,
                                        Stock.CANCEL, ResponseType.CANCEL)
        //filter.add_pixbuf_formats ()
        //dialog.add_filter (filter)

        case dialog.run()
            when ResponseType.ACCEPT
                var filename = dialog.get_filename()
                //image.set_from_file(filename)

        if (dialog.run () == Gtk.ResponseType.ACCEPT)
            _uri = dialog.get_uri ()
            stdout.printf ("Selection:\n %s", _uri)

        dialog.destroy ()

或者我根本不应该担心 _variables 的累积?

4

1 回答 1

3

首先是关于术语的注释,然后是概括。

可以在程序中的任何位置访问“全局变量”,因此它的范围是全局的。_variables您在问题中所指的是对象范围内的私有字段。它们只能由该对象中定义的代码访问。但是,您有权关注对象中私有工作变量的累积。

设计对象很难做到,技术和想法在几十年的实践和研究中不断发展。SOLID首字母缩略词由 Michael Feathers 介绍,总结了面向对象设计的五个原则,为评估您的设计提供了有用的标准。还有这本书,设计模式:可重用的面向对象软件的元素,由 Gamma 等人撰写。并于 1994 年首次出版,对面向对象编程中的设计进行了很好的总结和分类。该书使用文档编辑器作为案例研究来演示此类模式的使用。书中的 SOLID 原则和设计模式都是抽象的,它们不会告诉你如何编写程序,但它们确实给出了一组通用的想法,供程序员讨论和评估。因此,我将在我的回答中使用这两种工具,但请注意近年来已经开发了其他技术来进一步增强软件开发过程,特别是测试驱动开发行为驱动开发

SOLID 中的 S 代表单一职责原则,是查看示例的良好起点。通过调用您的对象,Application并将私有工作变量视为全局变量,这表明您正在将整个应用程序编写在单个对象中。您可以做的是开始将其分成Application多个不同的对象,这些对象更多地关注单一的责任领域。首先,虽然我认为我会重命名该Application对象。我去了EditorWindow。在我下面的示例中EditorWindow也有 aHeader和 a DocumentView

编译下面的代码:

valac -X -DGETTEXT_PACKAGE --pkg gtk+-3.0 text_editor_example.gs

-X -DGETTEXT_PACKAGE在这个答案的末尾解释了使用。

[indent=4]
uses
    Gtk

init
    Intl.setlocale()
    Gtk.init( ref args )

    var document = new Text( "Lorem Ipsum" )

    var header = new Header( "My text editor" )
    var body = new DocumentView( document )
    var editor = new EditorWindow( header, body )

    var document_selector = new DocumentFileSelector( editor )
    var load_new_content_command = new Load( document, document_selector )
    header.add_item( new OpenButton( load_new_content_command ) )

    editor.show_all()
    Gtk.main()

class EditorWindow:Window
    construct( header:Header, body:DocumentView )
        this.window_position = WindowPosition.CENTER
        this.set_default_size( 400, 400 )
        this.destroy.connect( Gtk.main_quit )

        this.set_titlebar( header )

        var box = new Box( Gtk.Orientation.VERTICAL, 1 )
        box.pack_start( body, true, true, 0 )
        this.add( box )

class Header:HeaderBar
    construct( title:string = "" )
        this.show_close_button = true
        this.set_title( title )

    def add_item( item:Widget )
        this.pack_start( item )

class OpenButton:ToolButton
    construct( command:Command )
        this.icon_widget = new Image.from_icon_name(
                                                 "document-open",
                                                 IconSize.SMALL_TOOLBAR
                                                 )
        this.clicked.connect( command.execute )

class DocumentView:ScrolledWindow
    construct( document:TextBuffer )
        var view = new TextView.with_buffer( document )
        view.set_wrap_mode( Gtk.WrapMode.WORD )
        this.add( view )

interface Command:Object
    def abstract execute()

interface DocumentSelector:Object
    def abstract select():bool
    def abstract get_document():string

class Text:TextBuffer
    construct ( initial:string = "" )
        this.text = initial

class DocumentFileSelector:Object implements DocumentSelector

    _parent:Window
    _uri:string = ""

    construct( parent:Window )
        _parent = parent

    def select():bool
        var dialog = new FileChooserDialog( "Open file",
                                            _parent,
                                            FileChooserAction.OPEN,
                                            dgettext( "gtk30", "_OK"),
                                            ResponseType.ACCEPT,
                                            dgettext( "gtk30", "_Cancel" ),
                                            ResponseType.CANCEL
                                           )

        selected:bool = false
        var response = dialog.run()
        case response
            when ResponseType.ACCEPT
                _uri = dialog.get_uri()
                selected = true

        dialog.destroy()
        return selected

    def get_document():string
        return "Reading the text from a URI is not implemented\n%s".printf(_uri)

class Load:Object implements Command

    _receiver:TextBuffer
    _document_selector:DocumentSelector

    construct( receiver:TextBuffer, document_selector:DocumentSelector )
        _receiver = receiver
        _document_selector = document_selector

    def execute()
        if _document_selector.select()
            _receiver.text = _document_selector.get_document()

图形用户界面的常见高级模式是模型-视图-控制器 (MVC)。这是关于解耦您的对象,以便可以轻松地重复使用和更改它们。在示例document中已成为表示模型的对象。通过使其成为一个单独的对象,它允许为相同的数据提供多个视图。例如,在编写 StackOverflow 问题时,您有一个编辑器窗口,但也有一个预览。两者都是相同数据的不同视图。

在示例中,标题工具栏已使用命令模式进一步分成不同的对象。工具栏中的每个按钮都有一个关联的命令。通过将命令作为单独的对象,可以重复使用命令。例如键绑定 Ctrl-O 也可以使用该Load命令。这样,附加到打开文档按钮的命令代码不需要重新编写以将其附加到 Ctrl-O。

命令模式使用接口。只要一个对象实现了该execute()方法,那么它就可以用作命令。该Load命令还利用对象的接口来询问用户要打开哪个 URI。Gtk+ 还提供了一个FileChooserNative。因此,如果您想切换到使用FileChooserNative对话框而不是对话框,FileChooserDialog您只需要编写一个实现DocumentSelector接口的新对象并将其传递给Load命令即可。通过以这种方式解耦对象,它使您的程序更加灵活,并且私有字段的使用仅限于每个对象。

作为旁注,在编译您的示例时有一些警告:warning: Gtk.Stock has been deprecated since 3.10. 此答案中的示例使用较新的方式:

  • 对于打开的文档图标,Stock Items 的 GNOME 开发人员文档声明“使用命名图标“document-open”或标签“_Open”。所以我用过document-open。这些名称来自freedesktop.org 图标命名规范
  • 对于文件选择器对话框中的 OK 按钮,GNOME Developer 文档声明“不要使用图标。使用标签“_OK”。” 前面的下划线表示它被国际化和翻译gettextgettext使用作为翻译文件的“域”。对于 GTK+3,域称为gtk30. 要gettext在编译程序时启用默认域的宏,需要将其传递给 C 编译器。这就是为什么-X -DGETTEXT_PACKAGE需要。在 Genie 程序Intl.setlocale()中还需要将语言环境设置为运行时环境。LC_ALL="zh_CN" ./text_editor_example完成此操作后,如果您安装了该语言环境,则使用类似运行程序的方法将显示中文的 OK 按钮
于 2016-04-23T11:51:37.710 回答