跳轉到內容

從壓縮包/托盤圖示筆記本程式設計

來自華夏公益教科書,開放的書籍,開放的世界

設計筆記本托盤圖示

[編輯 | 編輯原始碼]

這個程式的想法很簡單:將剪貼簿上的任何文字儲存在一個方便的小托盤圖示上,它就會被儲存到 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
 ← 列印 托盤圖示筆記本 後記 → 
華夏公益教科書