記得前一次,老周給大夥,不,小夥伴們介紹了如何填寫 .resw 檔案,并且在 XAML 中使用 x:Uid 标記來加載。也順便給大夥兒分析了運作時是如何解析 .resw 檔案的。
本來說好了,後續老周會寫一篇關于如何在代碼裡面手動加載文本資源的博文,但一直拖到今天,因為老周前陣子在忙着開發自己的 UWP 應用,已經向應用商店送出了一個版本,昨天剛送出完一次更新。
好,今天咱們就聊聊代碼加載文本資源的事情。
在 XAML 中使用 uid 加載資源雖然友善,但是它有個缺點——不同控件有不同的屬性,有時候不太友善比對,當然了,如果你的資源所針對的控件類型不多,那是無所謂的。
為了彌補 uid 加載的不足,我們完全可以自己來編寫資源加載代碼。這種做法向來是老周慣用的。大家應該還記得當年的 WinForm 應用吧,它也是可以在設計器中直接翻譯和編輯資源的(.resx),然後 VS 會幫我們生成一個管理資源的類。
在實際開發中,老周一向不用這一招的,一般是自己寫資源類的,這樣很靈活,可以自由控制,再讓資源類公開一些屬性,然後與 UI 進行綁定即可。在WPF中老周并沒有開發過多語言的應用,是以沒怎麼去弄。
同樣的道理,在UWP應用中我們也照樣可以自己去封裝,然後與 UI 綁定即可,這樣自己管理起來也友善,而且可以同時用于 XAML 與非 XAML 代碼上。故,還是很有意義的。有時候,多折騰一下也是好的,愛折騰的人生更精彩。是以,去年春天老周學 PR,夏天學AE,秋天學CAD,冬天學葫蘆絲。今年過年時學單反相機,清明節後學巴烏,勞動節後學電器維修,請在瓦斯公司工作的同學教我安裝瓦斯竈,七月份學陶笛,九月份去看老師傅做絲綢卷畫,十月份臨摹柳公權的《玄秘塔碑》。
人一旦有事情可做,就不會胡思亂想了。
下面老周示範一個例子。
項目中放兩個資源檔案,一個是英文資源,一個是中文資源。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIyZuBnL4cjNyIDMwETNtAjNxMDM5ADOxUjMwEzNxAjMtkDOzcjNz8CXwEzNxAjMvwVO4MzN2MzLcd2bsJ2Lc12bj5ycn9Gbi52YucTMwIzcldWYtl2Lc9CX6MHc0RHaiojIsJye.png)
資源準備完成後,咱們開始封裝。其實,在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 月份時,SDK開發團隊已收到這個問題報告,将來會修複的。反正秋季更新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 應用了,性能高,能夠很好适應高分屏,以及各操作方式,最重要的是它安全。