天天看點

【Win 10 應用開發】在代碼中加載文本資源

記得前一次,老周給大夥,不,小夥伴們介紹了如何填寫 .resw 檔案,并且在 XAML 中使用 x:Uid 标記來加載。也順便給大夥兒分析了運作時是如何解析 .resw 檔案的。

本來說好了,後續老周會寫一篇關于如何在代碼裡面手動加載文本資源的博文,但一直拖到今天,因為老周前陣子在忙着開發自己的 UWP 應用,已經向應用商店送出了一個版本,昨天剛送出完一次更新。

好,今天咱們就聊聊代碼加載文本資源的事情。

在 XAML 中使用 uid 加載資源雖然友善,但是它有個缺點——不同控件有不同的屬性,有時候不太友善比對,當然了,如果你的資源所針對的控件類型不多,那是無所謂的。

為了彌補 uid 加載的不足,我們完全可以自己來編寫資源加載代碼。這種做法向來是老周慣用的。大家應該還記得當年的 WinForm 應用吧,它也是可以在設計器中直接翻譯和編輯資源的(.resx),然後 VS 會幫我們生成一個管理資源的類。

在實際開發中,老周一向不用這一招的,一般是自己寫資源類的,這樣很靈活,可以自由控制,再讓資源類公開一些屬性,然後與 UI 進行綁定即可。在WPF中老周并沒有開發過多語言的應用,是以沒怎麼去弄。

同樣的道理,在UWP應用中我們也照樣可以自己去封裝,然後與 UI 綁定即可,這樣自己管理起來也友善,而且可以同時用于 XAML 與非 XAML 代碼上。故,還是很有意義的。有時候,多折騰一下也是好的,愛折騰的人生更精彩。是以,去年春天老周學 PR,夏天學AE,秋天學CAD,冬天學葫蘆絲。今年過年時學單反相機,清明節後學巴烏,勞動節後學電器維修,請在瓦斯公司工作的同學教我安裝瓦斯竈,七月份學陶笛,九月份去看老師傅做絲綢卷畫,十月份臨摹柳公權的《玄秘塔碑》。

人一旦有事情可做,就不會胡思亂想了。

下面老周示範一個例子。

項目中放兩個資源檔案,一個是英文資源,一個是中文資源。

【Win 10 應用開發】在代碼中加載文本資源
在中文資源中,輸入以下三項内容。
【Win 10 應用開發】在代碼中加載文本資源
在英文資源中,輸入以下三項。
【Win 10 應用開發】在代碼中加載文本資源

資源準備完成後,咱們開始封裝。其實,在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 是很管用的,一改就靈,實時重新整理。

好了,大功告成,看看效果如何。

【Win 10 應用開發】在代碼中加載文本資源

示例源代碼下載下傳位址

OK,今天的文章就寫到這裡了,改天有新的發現,老周會及時分享。我現在是越來越喜歡 UWP 應用了,性能高,能夠很好适應高分屏,以及各操作方式,最重要的是它安全。

繼續閱讀