標籤:輸入 檔案名稱 標記 維修 執行個體 return family direct 有一個
記得前一次,老周給大夥,不,小夥伴們介紹了如何填寫 .resw 檔案,並且在 XAML 中使用 x:Uid 標記來載入。也順便給大伙兒分析了運行時是如何解析 .resw 檔案的。
本來說好了,後續老周會寫一篇關於如何在代碼裡面手動載入文本資源的博文,但一直拖到今天,因為老周前陣子在忙著開發自己的 UWP app,已經嚮應用商店提交了一個版本,昨天剛提交完一次更新。
好,今天咱們就聊聊代碼載入文本資源的事情。
在 XAML 中使用 uid 載入資源雖然方便,但是它有個缺點——不同控制項有不同的屬性,有時候不太方便匹配,當然了,如果你的資源所針對的控制項類型不多,那是無所謂的。
為了彌補 uid 載入的不足,我們完全可以自己來編寫資源載入代碼。這種做法向來是老周慣用的。大家應該還記得當年的 WinForm 應用吧,它也是可以在設計器中直接翻譯和編輯資源的(.resx),然後 VS 會幫我們產生一個管理資源的類。
在實際開發中,老周一向不用這一招的,一般是自己寫資源類的,這樣很靈活,可以自由控制,再讓資源類公開一些屬性,然後與 UI 進行綁定即可。在WPF中老周並沒有開發過多語言的應用,所以沒怎麼去弄。
同樣的道理,在UWP應用中我們也照樣可以自己去封裝,然後與 UI 綁定即可,這樣自己管理起來也方便,而且可以同時用於 XAML 與非 XAML 代碼上。故,還是很有意義的。有時候,多折騰一下也是好的,愛折騰的人生更精彩。所以,去年春天老周學 PR,夏天學AE,秋天學CAD,冬天學葫蘆絲。今年過年時學單反相機,清明節後學巴烏,勞動節後學電器維修,請在瓦斯公司工作的同學教我安裝燃氣灶,七月份學陶笛,九月份去看老師傅做絲綢卷畫,十月份臨摹柳公權的《玄秘塔碑》。
人一旦有事情可做,就不會胡思亂想了。
下面老周示範一個例子。
項目中放兩個資源檔,一個是英文資源,一個是中文資源。
在中文資源中,輸入以下三項內容。
在英文資源中,輸入以下三項。
資源準備完成後,咱們開始封裝。其實,在UWP中,載入資源有個很NND簡單的方法,就是用 Windows.ApplicationModel.Resources 命名空間下的
ResourceLoader 類。這個類很好用,調用靜態 GetForCurrentView 方法就能得到一個執行個體,然後用 GetString 方法就可以載入到字串資源了。
注意,GetForCurrentView 方法有兩個重載,一個是帶參數的,參數指定你的.resw 檔案名稱,不帶路徑和副檔名,這個在前一篇鳥文中老周介紹過的,比如,本例中,資源檔為 Goods.<language tag>.resw,所以傳遞的參數就是 Goods。另一個是不帶參數的 GetForCurrentView方法,如果調用這個版本的重載,那麼,你的 .resw 檔案必須命名為 Resources.resw,如 Resources.lang-zh-cn.resw,Resources.lang-zh-tw.resw 等。
先看看封裝好的類。
public class ResourcesItems : INotifyPropertyChanged { static readonly ResourcesItems mInstance = new ResourcesItems(); public static ResourcesItems Current { get { return mInstance; } } private ResourcesItems() { // 構造時載入一下,填充預設值 Load(); } public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propname) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propname)); } public void Load() { var loader = ResourceLoader.GetForCurrentView("Goods"); Item1 = loader.GetString("t1"); Item2 = loader.GetString("t2"); Item3 = loader.GetString("t3"); } #region 屬性 string _it1, _it2, _it3; public string Item1 { get { return _it1; } set { if (value != _it1) { _it1 = value; OnPropertyChanged(nameof(Item1)); } } } public string Item2 { get { return _it2; } set { if (_it2 != value) { _it2 = value; OnPropertyChanged(nameof(Item2)); } } } public string Item3 { get { return _it3; } set { if (_it3 != value) { _it3 = value; OnPropertyChanged(nameof(Item3)); } } } #endregion }
這裡我用 Item1、Item2和 Item3 三個屬性分別對應資源檔中的三個項。實現 INotifyPropertyChanged 介面是很有劃時代戰略意義的,這樣當語言改變時,從資源檔中重新載入文本時,UI 上的綁定可以即時獲得最新的值。
在 99.99986% 的應用情境中,我們只需要執行個體化一次資源類就行了,所以,保持它在應用生命週期中只有一個執行個體即可,沒必要建立那麼執行個體,浪費食物鏈資源。故而可以把建構函式私人化,然後用一個靜態、唯讀屬性來公開當前類的執行個體。即
static readonly ResourcesItems mInstance = new ResourcesItems(); public static ResourcesItems Current { get { return mInstance; } }
這個類可以公開一個方法,讓外部調用,來載入資源。
public void Load() { var loader = ResourceLoader.GetForCurrentView("Goods"); Item1 = loader.GetString("t1"); Item2 = loader.GetString("t2"); Item3 = loader.GetString("t3"); }
回到應用程式頁面類,比如項目模板產生的 MainPage 類,把這個資源管理類作為一個屬性公開一下。
ResourcesItems TheRes { get { return ResourcesItems.Current; } }
有人會說,老周,你幹嗎這麼多此一舉?因為待會兒我要用 x:Bind 來綁定,但是,XAML 編譯有個“八阿哥”,當你用x:Bind間接綁到執行個體上時,會發生編譯錯誤。據說在今年 7 月份時,SDKTeam Dev已收到這個問題報告,將來會修複的。反正秋季更新1709,16299 中還沒修複。
就是,如果你這樣綁定會出錯。
<Pig Head = "{x:Bind ResourceItems.Current.Item1}" ... />
所以,為了避開這個缺陷,可以在頁面類中封一下,然後我們可以把 x bind 的源指向頁面類的這個屬性,這樣就不會報錯了。
<TextBlock> <Run Text="{x:Bind TheRes.Item1,Mode=OneWay}" FontSize="16" Foreground="Blue"/> <Run Text="{x:Bind TheRes.Item2,Mode=OneWay}" FontSize="16" Foreground="Red"/> <Run Text="{x:Bind TheRes.Item3,Mode=OneWay}" FontSize="16" Foreground="DarkBlue"/> </TextBlock>
這樣綁定之後,我們就可以在代碼中即時修改應用程式的語言,然後再讓剛剛封裝的資源類重新載入文本就行了。
這裡,刻意封裝了一個方法,用改應用程式的當前語言。
private void SetLang(string lang) { var context = Windows.ApplicationModel.Resources.Core.ResourceContext.GetForCurrentView(); var qs = context.QualifierValues; // 這樣也可以修改當前應用的語言 qs["Language"] = lang; }
ResourceContext 負責應用內容相關的資源設定,其中,我們有兩個辦法來改語言,一個是改 Languages 屬性,注意它是個列表,也就是說你可以同時設定一串語言群組合,預設應用的應該是列表中的第一個(提前是它與當前 Win 10 系統的語言相同)。
不過,我選擇用第二種方法,就是從 QualifierValues 屬性中獲得一個字典,這個字典裡面的條目是用於描述當前應用程式資源的限定符,Key 是限定符名稱,Value 當然是對應的值。啥是限定符呢。比如當前應用的語言,螢幕縮放比例,是否高對比,是否旋轉螢幕等。
你如果好奇裡面有些啥玩意兒,可以用以下代碼來輸出一下。
#if DEBUG var strbd = new System.Text.StringBuilder(); foreach (var item in qs) { string ts = $"{item.Key} = {item.Value}"; strbd.AppendLine(ts); } System.Diagnostics.Debug.WriteLine($"\n\n{strbd}\n\n");#endif
然後你會看到類似以下這樣的輸出。
Language = zh-cn
Contrast = standard
Scale = 100
HomeRegion = CN
TargetSize =
LayoutDirection = LTR
Theme = dark
AlternateForm =
DXFeatureLevel =
Configuration =
DeviceFamily = Desktop
Custom =
看到以上輸出,你懂了吧。如果還不懂,那撞牆吧。
我們這裡只需要改 Language 的值就行了。所以
var qs = context.QualifierValues; // 這樣也可以修改當前應用的語言 qs["Language"] = lang;
於是,有人肯定又問老周了,老周你是不是失憶了?上一篇中你不是介紹了一個 Windows.Globalization.ApplicationLanguages 類嗎,裡面只要改一下 PrimaryLanguageOverride 屬性就可以了。對的,那個屬性確實爽用,一行程式碼完成。但是,那個屬性只適合於非即時更改,就是,可能只是應用運行時,或者使用者在設定時改一下。因為那個屬性修改了以後,不會馬上生效的,可能要等 3 秒鐘後才會重新整理,除非你故意讓程式卡 3 秒鐘。不然的話,你要是想讓切換語言馬上生效,就要改資源限定符了。改限定符 Language 是很管用的,一改就靈,即時重新整理。
好了,大功告成,看看效果如何。
樣本原始碼
OK,今天的文章就寫到這裡了,改天有新的發現,老周會及時分享。我現在是越來越喜歡 UWP app了,效能高,能夠很好適應高分屏,以及各操作方式,最重要的是它安全。
【Win 10 應用開發】在代碼中載入文本資源