跳转到内容

从压缩包/托盘图标笔记本编程

来自维基教科书,开放的书籍,开放的世界

设计笔记本托盘图标

[编辑 | 编辑源代码]

这个程序的想法很简单:将剪贴板上的任何文本保存在一个方便的小托盘图标上,它就会被保存到 SQLite 数据库中。看起来没什么变化,但文本被保存在一个新的记录中。

还可以搜索已经保存的笔记。当您在托盘图标上单击鼠标中键时,会弹出一个窗口。我更希望它在右键单击图标时出现,但右键单击只能显示菜单。这就是右键单击的作用:它们显示上下文菜单。现在,如果您有一个带有滚轮或按钮的鼠标,可以单击中键。对于那些使用笔记本电脑触控板的人来说,中键单击可以通过同时单击左右按钮来模拟。在 KDE 中,系统设置中有一个选项可以禁用此功能,但默认情况下它是启用的。

如果您愿意,可以尝试在应用程序启动时隐藏主窗口,或者不使用 skiptaskbar 属性,这样最小化的主窗口就不会在面板中与其他打开的应用程序一起列出,但这些效果并不吸引人,因此它们保持默认设置。

通过 Google 图片搜索可以找到适合托盘图标的图片

Tray Icon

它不需要是任何特定的尺寸。它在程序运行时会很好地自动调整大小。

窗口有一个大的文本区域来显示笔记,以及左上角的一个文本框,用于输入搜索文本。还有三个标签显示保存笔记的日期/时间、笔记在使用该搜索文本找到的所有笔记中的位置,以及为了便于查看,笔记的记录 ID。

Screenshot of Gambas Notebook main form

Main form for Gambas Notebook program

托盘图标(左下角)可以放置在任何位置。它不会出现在窗口中。它出现在系统托盘中(通常位于屏幕的右下角,在 KDE 的默认面板中)。

托盘图标有自己的组件,因此请检查项目 > 属性以查看它和数据库组件是否已包含。

Components needed for Gambas Notebook program

总之,要使用此笔记本

  1. 将任何文本复制到剪贴板。
  2. 单击托盘图标,您的文本将被保存。
  3. 同时单击鼠标左键和右键单击托盘图标,以搜索笔记。

有时,保存文本然后打开窗口并添加一些关键词来帮助您再次找到笔记会很有用。我有时会添加 JOKE、QUOTE 或 FAMILY HISTORY 等词语。这样,通过在搜索框中键入“QUOTE”,我所有的引用都会出现,我可以一次查看一个。复制所有选定的笔记将是一个有用的功能。

出于便于查看的目的,SQL select 语句将出现在窗口标题中。

为了确保完善,文本菜单有四个条目用于调整文本

Gambas Notebook Text Menu

输入更多... 将光标定位在可见笔记文本的末尾,以便您输入更多内容。

整理 将多个空格、多个制表符和多个空行删除,并将开头和结尾的空格删除。

句子 将断句合并。从电子邮件中复制的文本通常有不同的行。

双空格 用空行分隔段落。

这些文本操作在没有选择文本的情况下会对整个文本进行操作,如果有选择文本,则会对选定的文本进行操作。快捷键在那里是因为我经常发现自己会连续快速执行最后三个操作来使文本看起来体面。

其他菜单是

Gambas Notebook Other Menus

数据库是一个 SQLite 文件。您可能想知道 OPEN 和 NEW 菜单项在哪里。为简单起见,程序在打开时会创建一个新的数据库,如果不存在与它预期名称和位置的数据库。它名为 Notebook.sqlite,它位于 Home 目录中。如果您愿意,可以很容易地更改代码以将其放置在 Documents 目录中。

文件 > 备份 是一个有用的菜单项,它将数据库文件复制到 Documents 文件夹中的 Backups 文件夹,并使用日期戳来显示文件创建的时间,并防止干扰之前的备份。

文件 > 重新编号 使记录 ID 连续,没有间隙。它和下面的 Vacuum 项是多余的。Vacuum 将整理数据库文件的内部结构。它使用 SQLite 的内置 vacuum 命令。

文件 > 退出 关闭应用程序。文件 > 隐藏 使窗口不可见。单击窗口上的关闭框也只会隐藏窗口;它不会关闭应用程序。为了实现此技巧,窗口的 Persistent 属性设置为 true。当单击窗口的关闭框时,窗口会持续存在,但不可见。

当您在搜索文本框中键入时,一个 SQL Select 语句会选择包含该文本的所有笔记。这是一个简单的搜索:它不会搜索包含任何单词的笔记,从最匹配到最不匹配排序。它搜索与键入的文本完全匹配的文本。当您键入每个字母时,都会执行搜索。mdb 模块中的一个公共属性 Public rs As Result 存储搜索结果。笔记 > 显示全部 清除搜索并查找所有笔记。这在程序启动时发生:所有笔记都被选中。

笔记 > 新建... 允许您键入一个新的笔记,当您离开文本区域时该笔记会被保存。

笔记 > 删除 从数据库中删除当前显示的笔记。

笔记 > 清除全部 从数据库中清除所有笔记。没有撤消操作,但有机会退出。

窗口属性是

排列 = 垂直

堆叠 = 上方

宽度 = 800

高度 = 500

需要设置的托盘图标 (ti1) 属性是

工具提示 = 单击以保存剪贴板文本 可见 = True

请务必将其设置为可见,否则您在运行程序时将看不到托盘图标,并且在隐藏最初显示的窗口后,您将无法以优雅的方式退出正在运行的程序。

堆叠 属性确保窗口始终位于其他窗口之上。例如,如果您单击网页浏览器的窗口,笔记本窗口不会被浏览器窗口覆盖,而是会始终位于最上面,漂浮在其之上。这对于实用程序类型的程序来说非常有用。

在查看代码之前,最后要说明的是使用键盘的箭头键。UP 和 DOWN 分别将您带到找到的笔记的第一条和最后一条。LEFT 和 RIGHT 分别将您在找到的笔记中向前或向后移动。“找到的笔记”是指那些包含您在文本框中键入的文本字符串的笔记,或者如果没有键入任何内容,则指所有笔记。

代码后面有一些注释。

与数据库相关的代码收集在一个名为 mdb 的模块中。

mdb 模块代码

[编辑 | 编辑源代码]
' Gambas module file

Public db1 As New Connection
Public rs As Result
Public SQLSel As String

Public Sub CreateDatabase()

  db1.Type = "sqlite"
  db1.host = User.home
  db1.name = ""

  'delete an existing Notebook.sqlite
  If Exist(User.home & "/Notebook.sqlite") Then
    Kill User.home & "/Notebook.sqlite"
  Endif

  'create Notebook.sqlite
  db1.Open
  db1.Databases.Add("Notebook.sqlite")
  db1.Close

End

Public Sub ConnectDatabase()

  db1.Type = "sqlite"
  db1.host = User.home
  db1.name = "Notebook.sqlite"
  db1.Open
  SelectAllNotes
  Debug("Notes file connected:<br>" & db1.Host &/ db1.Name & "<br><br>" & rs.Count & " records")

End

Public Sub MakeTable()

  Dim hTable As Table

  db1.name = "Notebook.sqlite"
  db1.Open
  hTable = db1.Tables.Add("Notes")
  hTable.Fields.Add("KeyID", db.Integer)
  hTable.Fields.Add("Created", db.Date)
  hTable.Fields.Add("Note", db.String)
  hTable.PrimaryKey = ["KeyID"]
  hTable.Update
  Message("Notes file created:<br>" & db1.Host &/ db1.Name)

End

Public Sub SelectAllNotes()

  rs = db1.Exec("SELECT * FROM Notes")
  FMain.Caption = "SELECT * FROM Notes"
  rs.MoveLast

End

Public Sub Massage(z As String) As String

  While InStr(z, "''") > 0 'this avoids a build-up of single apostrophes
    Replace(z, "''", "'")
  Wend
  Return Replace(z, "'", "''")

End

Public Sub AddRecord(s As String, t As Date) As String

  Dim rs1 As Result
  Dim NextID As Integer

  If rs.Max = -1 Then NextID = 1 Else NextID = db1.Exec("SELECT Max(KeyID) AS TheMax FROM Notes")!TheMax + 1

  db1.Begin
  rs1 = db1.Create("Notes")
  rs1!KeyID = NextID
  rs1!Created = t 'time
  rs1!Note = Massage(s)
  rs1.Update
  db1.Commit
  SelectAllNotes
  Return NextID

Catch
  db1.Rollback
  Message.Error(Error.Text)

End

Public Sub UpdateRecord(RecNum As Integer, NewText As String)

  db1.Exec("UPDATE Notes SET Note='" & Massage(NewText) & "' WHERE KeyID=" & RecNum)
  Dim pos As Integer = rs.Index
  'Refresh the result cursor, so the text in it is updated as well as in the database file. This is tricky.
  If IsNull(SQLSel) Then rs = db.Exec("SELECT * FROM Notes") Else rs = db.Exec(SQLSel) 'SQLSel is the last search, set by typing in tbSearch
  rs.MoveTo(pos) 'Ooooh yes! It did it.

Catch
  Message.Error("<b>Update error.</b><br><br>" & Error.Text)

End

Public Sub MoveRecord(KeyCode As Integer) As Boolean

  If rs.Count = 0 Then Return False
  Select Case KeyCode
    Case Key.Left
      If rs.Index > 0 Then rs.MovePrevious Else rs.MoveLast
      Return True
    Case Key.Right
      If rs.Index < rs.Max Then rs.MoveNext Else rs.MoveFirst
      Return True
    Case Key.Up
      rs.MoveFirst
      Return True
    Case Key.Down
      rs.MoveLast
      Return True
  End Select
  Return False

End

Public Sub ClearAll()

  If Message.Warning("Delete all notes? This cannot be undone.", "Ok", "Cancel") = 1 Then
    db1.Exec("DELETE FROM Notes")
    SelectAllNotes
  Endif

End

Public Sub DeleteRecord(RecNum As Integer)

  db1.Exec("DELETE FROM Notes WHERE KeyID='" & RecNum & "'")
  SelectAllNotes

End

Public Sub SearchFor(s As String)

  SQLSel = "SELECT * FROM Notes WHERE Note LIKE '%" & Massage(s) & "%'"

  If IsNull(s) Then
    SelectAllNotes
    SQLSel = ""
  Else
    FMain.Caption = SQLSel
    rs = db1.Exec(SQLSel)
  Endif

End

Public Sub Renumber()

  Dim res As Result = db.Exec("SELECT * FROM Notes ORDER BY KeyID")
  Dim i As Integer = 1
  Dim x As Integer

  Application.Busy += 1
  While res.Available
    x = res!KeyID
    db.Exec("UPDATE Notes SET KeyID=" & i & " WHERE KeyID=" & x)
    i += 1
    res.MoveNext
  Wend
  SelectAllNotes
  Application.Busy -= 1

End

Public Sub Vacuum() As String

  Dim fSize1, fSize2 As Float

  fSize1 = Stat(db1.Host &/ db1.Name).Size / 1000 'kB
  db1.Exec("Vacuum")
  fSize2 = Stat(db1.Host &/ db1.Name).Size / 1000 'kB
  Dim Units As String = "kB"
  If fSize1 > 1000 Then 'megabyte range
    fSize1 /= 1000
    fSize2 /= 1000
    Units = "MB"
  Endif
  Return Format(fSize1, "#.0") & Units & " -> " & Format(fSize2, "#.0") & Units & " (" & Format(fSize1 - fSize2, "#.00") & Units & ")"

End

Public Sub Backup() As String

  If Not Exist(User.Home &/ "Documents/Backups/") Then Mkdir User.Home &/ "Documents/Backups"
  Dim fn As String = "Notebook " & Format(Now, "yyyy-mm-dd hh-nn")
  Dim source As String = db1.Host &/ db1.Name
  Dim dest As String = User.Home &/ "Documents/Backups/" & fn
  Try Copy source To dest
  If Error Then Return "Couldn't save -> " & Error.Text Else Return "Saved ->  /Documents/Backups/" & fn

End

主窗体的代码

[编辑 | 编辑源代码]
' Gambas class file

Public OriginalText As String

Public Sub ti1_Click()

  Dim TimeAdded As String

  If Clipboard.Type = Clipboard.Text Then TimeAdded = mdb.AddRecord(Clipboard.Paste("text/plain"), Now())

End

Public Sub Form_Open()

  If Not Exist(User.Home &/ "Notebook.sqlite") Then 'create notebook data file
    mdb.CreateDatabase
    mdb.MakeTable
    mdb.SelectAllNotes
  Else
    mdb.ConnectDatabase
    ShowRecord
  Endif

End

Public Sub MenuQuit_Click()

  mdb.db1.Close
  ti1.Delete
  Quit

End

Public Sub Form_KeyPress()

  If mdb.MoveRecord(Key.Code) Then
    ShowRecord
    Stop Event
  Endif

End

Public Sub ShowRecord()

  If mdb.rs.count = 0 Then
    ClearFields
    Return
  Endif
  ta1.Text = Replace(mdb.rs!Note, "''", "'")
  Dim d As Date = mdb.rs!Created
  labTime.text = Format(d, gb.MediumDate) & "  " & Format(d, gb.LongTime)
  labRecID.text = mdb.rs!KeyID
  labLocation.text = Str(mdb.rs.Index + 1) & "/" & mdb.rs.Count
  OriginalText = ta1.Text

End

Public Sub MenuClear_Click()

  mdb.ClearAll
  ClearFields
  labTime.Text = "No records"

End

Public Sub MenuCopy_Click()

  Clipboard.Copy(ta1.Text)

End

Public Sub MenuDeleteNote_Click()

  Dim RecNum As Integer = Val(labRecID.Text)

  mdb.DeleteRecord(RecNum) 'after which all records selected; now to relocate...
  Dim res As Result = db.Exec("SELECT * FROM Notes WHERE KeyID<" & RecNum)
  res.MoveLast
  Dim i As Integer = res.Index
  mdb.rs.MoveTo(i)
  ShowRecord

End

Public Sub ClearFields()

  ta1.Text = ""
  labRecID.Text = ""
  labTime.Text = ""
  labLocation.Text = ""

End

Public Sub MenuNewNote_Click()

  ClearFields
  ta1.SetFocus

End

Public Sub ta1_GotFocus()

  OriginalText = ta1.Text

End

Public Sub ta1_LostFocus()

  If ta1.Text = OriginalText Then Return 'no change
  SaveOrUpdate

End

Public Sub tbSearch_Change()

  mdb.SearchFor(tbSearch.Text)
  ShowRecord

Catch
  Message.Error(Error.Text)

End

Public Sub ta1_KeyPress()

  If Key.Code = Key.Esc Then Me.SetFocus 'clear focus from textarea; this triggers a record update

End

Public Sub tbSearch_KeyPress()

  If Key.Code = Key.Esc Then Me.SetFocus

End

Public Sub MenuShowAll_Click()

  mdb.SelectAllNotes
  ShowRecord

End

Public Sub KeepReplacing(InThis As String, LookFor As String, Becomes As String) As String

  Dim z As String = InThis

  While InStr(z, LookFor) > 0
    z = Replace(z, LookFor, Becomes)
  Wend
  Return z

End

Public Sub SaveOrUpdate()

  If IsNull(labRecID.Text) Then 'new record
    If IsNull(ta1.Text) Then Return
    Dim d As Date = Now()
    labRecID.Text = mdb.AddRecord(ta1.Text, d)
    labTime.text = Format(d, gb.MediumDate) & "  " & Format(d, gb.LongTime)
  Else 'update
    If IsNull(ta1.Text) Then
      mdb.DeleteRecord(Val(labRecID.Text))
      ClearFields 'maybe leave everything empty?
    Else
      mdb.UpdateRecord(Val(labRecID.Text), ta1.Text)
    Endif
  Endif

End

Public Sub MenuTidy_Click()

  OriginalText = ta1.Text
  If IsNull(ta1.Text) Then Return
  Dim z As String = If(ta1.Selection.Length = 0, Trim(ta1.Text), Trim(ta1.Selection.Text))
  z = KeepReplacing(z, gb.NewLine, "|")
  z = KeepReplacing(z, gb.Tab & gb.Tab, gb.Tab)
  z = KeepReplacing(z, "  ", " ")
  z = KeepReplacing(z, "| ", "|")
  z = KeepReplacing(z, "|" & gb.tab, "|")
  z = KeepReplacing(z, "||", "|")
  z = KeepReplacing(z, "|", gb.NewLine)
  If ta1.Selection.Length = 0 Then ta1.Text = z Else ta1.Selection.Text = z
  SaveOrUpdate

End

Public Sub MenuSentences_Click()

  OriginalText = ta1.Text
  If IsNull(ta1.Text) Then Return
  Dim z As String = If(ta1.Selection.Length = 0, Trim(ta1.Text), Trim(ta1.Selection.Text))
  z = KeepReplacing(z, gb.NewLine, "~")
  z = KeepReplacing(z, "~ ", "~")
  z = KeepReplacing(z, ".~", "|")
  z = KeepReplacing(z, "~", " ")
  z = KeepReplacing(z, "  ", " ")
  z = KeepReplacing(z, "|", "." & gb.NewLine)
  If ta1.Selection.Length = 0 Then ta1.Text = z Else ta1.Selection.Text = z
  SaveOrUpdate

End

Public Sub MenuUndo_Click()

  Dim z As String = ta1.Text

  ta1.Text = OriginalText
  OriginalText = z
  SaveOrUpdate

End

Public Sub MenuRenumber_Click()

  mdb.Renumber
  ShowRecord

End

Public Sub MenuVacuum_Click()

  Me.Caption = "File size -> " & mdb.Vacuum()

End

Public Sub MenuBackup_Click()

  Me.Caption = mdb.Backup()

End

Public Sub MenuTypeExtra_Click()

  ta1.SetFocus
  ta1.Text &= gb.NewLine & gb.NewLine
  ta1.Select(ta1.Text.Len)

End

Public Sub MenuDoubleSpace_Click()

  OriginalText = ta1.Text
  If IsNull(ta1.Text) Then Return
  Dim z As String = If(ta1.Selection.Length = 0, Trim(ta1.Text), Trim(ta1.Selection.Text))
  z = KeepReplacing(z, gb.NewLine, "|")
  z = KeepReplacing(z, "||", "|")
  z = KeepReplacing(z, "|", gb.NewLine & gb.NewLine)
  If ta1.Selection.Length = 0 Then ta1.Text = z Else ta1.Selection.Text = z
  SaveOrUpdate

End

Public Sub ti1_MiddleClick()

  Me.Show
  Me.Activate
  tbSearch.Text = ""
  mdb.SelectAllNotes
  ShowRecord

End

Public Sub MenuHide_Click()

  Me.Hide

End

两个有用的函数

[编辑 | 编辑源代码]

Massage(string) 函数对于处理包含单引号的文本的保存是必要的。SQL 语句使用单引号来包围字符串。字符串中的单引号将终止字符串,后面的内容将是语法错误,因为它将是不可理解的。要包含单引号,它必须加倍。例如,要保存字符串 Fred’s house,它必须首先被转换为(“按摩”为)Fred’’s house

KeepReplacing(InThis, LookFor, ReplaceWithThis) 函数执行替换,直到 LookFor 字符串不再存在。例如,如果您想从 abcxxxxdef 中删除多个 x,只保留一个 x,您不能只使用 Replace(“abcxxxxdef”, “xx”, “x”),因为这将产生 abcxxdef。第一个双 x 变成一个 x,第二个双 x 变成一个 x。您仍然有两个 x。您必须不断替换,直到不再存在双 x。

好了,各位,除了参考附录。我可以从开始的地方结束,向 Benoît Minisini 表示感谢。这个编程环境非常令人愉快。我们所有用户都怀着感激之情,举杯高歌,“因为他是一位快乐的绅士,我们大家都这么说”。

Gerard Buzolic

2019 年 9 月 25 日

从压缩包编程 Gambas
 ← 打印 托盘图标笔记本 后记 → 
华夏公益教科书