問題來源:
假設我有一個數據對象 TnUser = class
裏面需要存儲圖片。
因爲 Delphi 的 VCL 和 FMX 對圖片的處理,引用的單元不同,爲了讓這個 TnUser 更單純,平臺中立,它只能存儲圖片的二進制數據,而不是 TBitmap 或者 TJpeg 這樣的對象。
因此,我在這裏直接用一個 TMemroyStream 來存儲圖片的數據。
問題來了,我在 ViewModel 裏面通過拖放 AdapterBindSource1 和 DataGeneratorAdapter1 並且在 DataGeneratorAdapter1 裏面定義對應 TnUser 的字段,圖片字段只有 Bitmap 類型。這裏面沒有 Blob 類型也沒有 Stream 類型的字段。
在 View 模塊也就是界面 Form 上面拖放一個 TImage,一個 BindingsList1,可視化創建 LiveBinding 綁定。設計期可以看到圖片。
程序運行時創建 TnUser 的實例,但不給 Photo (TMemoryStream) 賦值。界面上另外兩個 string 類型的字段顯示出來了,沒有圖。到這裏一切順利。
點擊界面按鈕,加載一個 JPEG 圖片到 FUser 的 Photo 字段,也就是把 Jpeg 文件的數據寫入 MemoryStream,刷新綁定,則出來一個類型轉換錯誤。Image 不能對應 Stream。
解決辦法
如何解決這個問題。當然我首先想到運行期做類型轉換。在界面上,雙擊 BindingsList1,彈出窗口,裏面就是可視化創建的連接的對象。選中綁定圖片的那個對象,在屬性面板裏面,切換到事件,可以看到幾個事件,其中兩個是:OnAssignedValue 和 OnAssigningValue,顯然應該在 OnAssigningValue 事件裏面寫代碼轉換數據。但因爲我還不熟悉 TValue 的操作,一時搞不定轉換的數據如何寫進去,於是開始想別的辦法。
以下是別的辦法的測試結果:
爲了讓數據對象 TnUser 平臺獨立,也就是不要包含 VCL 或者 FMX 的代碼,
其中的 Photo 採用 TStream 直接存儲數據。
如果把這個 Photo 屬性直接 LiveBinding 到界面上的 Image,則運行時會說類型轉換錯誤。
那麼,這裏如何插入我自己寫的類型轉換代碼?
在 LiveBinding 的框架裏面,暫時找不到地方插入代碼。也搜索不到相關文檔。
--- 搞定。2020-2-13 凌晨 3 點。類型轉換隻需要在連接的事件 OnAssigningValue 裏面寫,去更改其 TValue 就可以了。
因此,我測試了以下方法:
1. 給 TnUser 增加一個 Helper 類,爲這個類增加一個 Photo2 屬性,這個屬性是 TBitmap;(本模塊是 ViewModel,可以平臺相關)
2. 綁定 Image 到 Photo2。但是,運行時,不會觸發到 Helper 類的方法。
2.1. 增加一個測試按鈕,測試 FUser.Photo2,可以觸發 Helper 類的方法,可以獲得 Photo2 的屬性並顯示出來,說明 Helper 類沒寫錯。
也就說明 LiveBinding 在執行綁定的時候不認識 Helper 類。
3. 本模塊自己重新引入相關屬性,然後綁定到本模塊而不是 FUser,運行時出異常;原因是 AdapterBindSource1.OnCreateAdapter 先於本模塊的創建。
3.1. 就算把 AdapterBindSource1.AutoActive 設置爲 True,它的事件方法依然先於本模塊的 Create 被觸發調用。
因此,不能採用設置 AutoActive 爲 False 然後在運行期設置爲 True 來解決這個問題。
4. 繼承 TnUser,在繼承的類裏面實現 Photo4 屬性,這個屬性是 TBitmap 或者 TJpeg,綁定此對象,都能顯示。
4.1. 爲了跨平臺而繼承,不是好的解決方法。
做完上面那些測試後,回到數據轉換,仔細查看 TValue,發現可以寫入數據。
正確的解決方法
雙擊界面上的 BindingsList1 彈出窗口:
選中上面關於圖片的連接,看看它的屬性面板裏面的事件:
雙擊 OnAssigningValue 事件,在 IDE 創建的代碼框架裏面寫如下代碼:
procedure TForm1.LinkControlToField1AssigningValue(Sender: TObject;
AssignValueRec: TBindingAssignValueRec; var Value: TValue;
var Handled: Boolean);
var
Jpg: TJpegImage;
begin
if Value.IsObject then
begin
if Value.AsObject is TStream then
begin
TStream(Value.AsObject).Position := 0;
Jpg := TJpegImage.Create;
try
Jpg.LoadFromStream(TStream(Value.AsObject));
Value := TValue.From(Jpg);
finally
//Jpg.DisposeOf; 這裏不能釋放 Jpg 對象,否則會 AV。因此,程序在這裏需要用一個 TForm1 裏面定義的 Jpg 對象反覆使用而不是每次都在這裏創建新的。
end;
end;
end;
end;
上述代碼搞定,完成了綁定的對象的屬性是 TStream 轉換爲目標需要的 TJpegImage 或者 TBitmap 的問題。這裏主要的問題是對 TValue 的操作。
在 VCL 框架下,這裏是 TJpegImage 或者是 TBitmap 都能夠讓界面上綁定的 TImage 正確顯示圖片。
這樣,就實現了數據對象的代碼平臺中立。而界面 View 肯定是和平臺相關的。連 ViewModel 都無需關注平臺相關代碼。
另:關於如何綁定一個對象,請參考俺的上一篇 Blog。