在 WPF 和 Silverlight 2 中反覆使用代碼

來源:互聯網
上載者:User

Dino Esposito

代碼下載位置: CuttingEdge2008_10.exe (604 KB)
線上瀏覽代碼

本專欄基於 Silverlight 2 的預發布版本。文中的所有資訊均有可能發生變更。

目錄

WPF 與 Silverlight 2 的相容性
視覺狀態管理器簡介
共用代碼
有關 WPF 應用程式的推論
編寫跨平台的 WPF 代碼
分析Managed 程式碼
擷取用於關鍵代碼的策略
樣本分析
最終注意事項

在 Silverlight 2 中,您可以使用可擴充應用程式標記語言 (XAML) 來設計和渲染使用者介面。與此同時,您可以利用內建的核心 CLR 處理瀏覽器內的Managed 程式碼。這樣基於 Web 的 Silverlight 2 應用程式與案頭 Windows Presentation Foundation (WPF) 應用程式就變得極為相似。編程模型相似的好處之一是可以在二者之間輕鬆重用代碼。在本專欄中,我將介紹幾種模式,它們能在 Silverlight 2 和 WPF 之間最為輕鬆地共用代碼和 XAML 標記。

Silverlight 中的 CoreCLR

Silverlight 2 確實包含一個 CLR,但並不是其他 .NET 應用程式和程式集使用的 CLR。Silverlight CLR 也稱為 CoreCLR,在設計時充分考慮了不同用途。CoreCLR 專為跨平台互通性而設計,可與 CLR 同時運行並支援不同的安全模型以及不同版本的基礎類庫。2008 年 8 月的出色專欄《CLR 全面透徹解析》中對此進行了詳細說明(請參閱 msdn.microsoft.com/magazine/cc721609)。

Silverlight 和 .NET 應用程式使用不同 CLR 意味著您不能在兩個針對 .NET 應用程式和 Silverlight 應用程式的項目中引用同一個程式集。主要問題出在 mscorlib 程式集上。Silverlight 正常使用所需的功能集非常小-僅僅是核心。但任何 .NET 程式集都要連結標準版本的 mscorlib,這就是問題所在。

在本專欄所討論的應用程式範例中,我利用一個介面來共用 Windows Presentation Foundation 應用程式與 Silverlight 應用程式。唯一的解決方案是在兩項目之間複製 C# 及介面定義,因為您沒有共同引用的程式集,因此在 .NET Framework 版本中,有必要將標準 mscorlib 程式集內的功能分成兩部分:核心事務和案頭事務,以便為 Silverlight 和 .NET 程式集之間的二進位相容性奠定基礎。

WPF 與 Silverlight 2 的相容性

繼引入 Silverlight 2 後,XAML 不動聲色地成為新一代的 UI 模組的 API。Silverlight 2 支援完整 WPF 架構的子集,其中包括豐富布局管理、資料繫結、樣式、媒體、動畫、圖形和模板功能。

但在 Silverlight 2 內完全支援 XAML 受可下載外掛程式的大小限制。在只有幾兆的空間內(Beta 2 中不到 5MB),Silverlight 2 外掛程式必須提供核心 CLR,即包括 WPF 和 Windows Communication Foundation (WCF) 用戶端平檯子集、XAML 分析器以及大量 Silverlight 特定控制項的 Microsoft .NET Framework 3.5。

在 Silverlight 2 中不支援 WPF 3D 圖形功能,其中的某些屬性和元素也已被丟棄或裁減。綜上所述,您已擁有可相容的子集,因此您可以製作 Silverlight 版本的較為複雜的 WPF 使用者介面。稍後,我將回頭繼續討論相容性的關鍵區段。

請注意,儘管 Microsoft 也在 Silverlight 中添加了某些新 API,但它們目前在案頭版本的 WPF 中並沒有相對應的功能。最相關的功能包括控制項(如 DataGrid)和類(如用於簡單的 GET 樣式網路調用的 WebClient)。這些功能也將被添加到 WPF 中。

視覺狀態管理器簡介

WPF 中還將添加 Silverlight 2 附帶的另一個極為出色的功能 – 用於控制項的視覺狀態管理器 (VSM)。VSM 通過引入視覺狀態和狀態轉換簡化了互動控制模板的開發。由於 WPF 的引入,開發人員可通過模板和樣式自訂 WPF 控制項的外觀和行為。例如,你不能只更改某個控制項的形狀和外觀,還應該定義該控制項在點擊、得到或失去焦點等事件時的新行為或動畫。

使用 VSM,視覺狀態和狀態轉換將替您完成其中的部分工作。典型的 VSM 視覺狀態有普通、滑鼠移至上方、禁用和獲得焦點。您可以為這些狀態每種定義一個樣式或形狀。狀態轉換用於定義控制項如何從一種狀態轉換到另一種可視狀態。通常可通過動畫定義轉換。在運行時,Silverlight 將播放相應的動畫並應用指定的樣式流暢地將控制項從一個狀態轉換到另一個狀態。

要定義視覺狀態,在控制項範本中插入標記片段,如下所示:

複製代碼

<vsm:VisualStateManager.VisualStateGroups>  <vsm:VisualStateGroup x:Name="CommonStates">    <vsm:VisualState x:Name="MouseOver">      <Storyboard>        ...      </Storyboard>    </vsm:VisualState>  </vsm:VisualStateGroup></vsm:VisualStateManager.VisualStateGroups>

每個控制項都定義一組或多組狀態,如 CommonStates 和 FocusStates。而每組都定義具體的視覺狀態,如“MouseOver”、“Pressed”和“Checked”。對於每種視覺狀態和狀態之間的轉換,您可以定義一個 Silverlight 可在適當時候自動播放的故事板。

簡言之,WPF 中有很多 Silverlight 2 不支援的功能,而 Silverlight 2 中的很多功能 WPF 也不具備。很大程度上是這些差異影響了 XAML 層級的相容性。您可以使用公用子集實現完全相容,並且令人欣慰的是,它足以保證您能執行幾乎所有的操作。

共用代碼

當案頭版本的 WPF 支援 VSM 時,您可以對 Silverlight 和 Windows 傳統型應用程式使用相同的方法,並共用 WPF 和 Silverlight 項目之間的控制模板。在此之前,讓我們先看看您現在是如何在 WPF 案頭和 Web 項目之間重用代碼的。

WPF 應用程式由 XAML 和Managed 程式碼組成。Managed 程式碼的目標是所支援版本的 .NET 的多個類。您應當使用一個案頭 WPF 和 Silverlight 2 都能理解的 XAML 公用子集。同樣地,您應當組織好您的程式碼後置類別,這樣就能很好地處理後端架構之間的差異。

您主要希望在兩個場合中重用 WPF 代碼。一種情況是您現在已有一個 WPF 傳統型應用程式,希望通過 Web 提供該程式以簡化維護和部署。另一種情況是您希望為現有的系統開發一個前端,並將其應用於 Windows 和 網頁用戶端。

我將從 WPF 到 Silverlight 的角度著手處理重用代碼的問題。在重構代碼和標記的模式方面,兩者之間有非常小的差異。

有關 WPF 應用程式的推論

典型的 WPF 應用程式是通過對象樹(Window 為樹的根)構建的。Window 元素又包含大量按照各種方式布置和堆放的子項目。這些元素涉及基本形狀、布局管理器、故事板和控制項(包括自訂的第三方和使用者控制項)。下面是一個基本樣本:

複製代碼

<Window x:Class="Samples.MyTestWindow"    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    Title="Test App" Height="300" Width="350">    <StackPanel Margin="10">        ...    </StackPanel></Window>

該代碼無法照原樣合并到 Silverlight 2 應用程式中。首先,Silverlight 中不支援 Window 元素。在 Silverlight 程式集中,根本沒有諸如 System.Windows 命名空間中的類。所有 Silverlight 應用程式的根標記都是 UserControl。該元素被映射到在 System.Windows.Controls 命名空間中定義的 UserControl 類上。

下面是一個標題被修改的 Silverlight 應用程式:

複製代碼

<UserControl x:Class="Samples.MyTestWindow"    xmlns="http://schemas.microsoft.com/client/2007"    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"    Title="Test App" Height="300" Width="350">    <StackPanel Margin="10">        ...    </StackPanel></UserControl>

<Window> 和 <UserControl> 元素邊界內的所有標記代碼應繼續匹配。您負責修改原始的案頭 XAML 使其符合 Silverlight XAML 分析器的要求。該任務會從兩種文法的高相容性受益良多。在多數情況下,調整 XAML 只是修複少數元素和屬性。舉兩個樣本進行說明。

案頭 WPF 中包含一個 Silverlight 無法識別的 <label> 元素。將此標記遷移到 Silverlight 中時,您需要為該標籤找到一個工作區。一個內部包含文字區塊的矩形是個較為可行的解決方案。

在 WPF 中,您可以使用 ToolTip 屬性將工具提示與控制項關聯起來,如下所示:

複製代碼

<Button Tooltip="Click me" ... />

在 Silverlight 2 中,不支援 ToolTip 屬性。您必須使用 ToolTipService,如以下程式碼片段所示:

複製代碼

<Button ToolTipService.ToolTip="Click me" ... />

應注意這兩種解決方案都是在案頭 WPF 中啟動並執行。另外,案頭 WPF 中的 ToolTipService 提供了大量的附加工具提示屬性,如位置、位移、初始延遲和期間。但 Silverlight 不支援其中任一項額外屬性。

這些都是 WPF 和 Silverlight 之間的相容性問題嗎?這取決於您使用 WPF 的方式。通常,將重要的 WPF 應用程式遷移到 Silverlight 中是很困難的,實際上這甚至不是我們的主要目的。首先,在 Silverlight 2 中沒有任何觸發器,而您可能在任何地方需要使用它們。例如,UI 元素中有一個觸發器集合 - FrameworkElement 的子代,但不是在樣式、資料和控制模板中。

同樣地,Silverlight 中支援資料繫結,但與在 WPF 中的支援方式不同。例如,您具有了 Binding 元素,即具有了資料環境、資料範本和可見集合。如前所述,您沒有任何觸發器,而簡化的 XAML 標記卻比在 WPF 中更需要您頻繁地編寫代碼。此外,內部實現也完全不同。與 WPF 相比,Silverlight 中的 Binding 對象的屬性要少很多。

全球化是可能導致您頭疼的另一方面。出於效能原因,核心 CLR 不包括其自己的所有支援地區的全球化資料。而 Silverlight 中的 CultureInfo 類依賴於由基礎作業系統提供的全球化功能。這意味著您無法跨越不同的作業系統給予應用程式同樣的全球化設定。

最後,WPF 包括一個更為豐富的控制項集,而 Silverlight 中則沒有。一個不錯的樣本是 RichTextBox 控制項。

總之,將 Silverlight 應用程式遷移 WPF 中沒有什麼價值,儘管作為開發人員您應當參與處理那些可能由豐富物件模型所導致的潛在效能問題。要牢記的一點事實是 Silverlight 支援案頭 WPF 提供的可能情況的子集,您負責決定是否可通過選擇適當的功能來設計出跨平台的解決方案。

編寫跨平台的 WPF 代碼

將代碼從 WPF 項目遷移到 Silverlight 項目中最簡單的辦法是使用使用者控制項。除 XML 命名空間截然不同和標記差異之外,使用使用者控制項是在案頭與基於 Web 的 WPF之間共用標記和代碼的唯一方法。

如果您的 WPF 應用程式是以本機形式組織的或可進行重構,充分地利用使用者控制項,將代碼遷移到 Silverlight 中要比其他方式(剪下和粘貼)簡單得多。

這樣 WPF 和 Silverlight 項目之間就可以實現程式集共用了嗎?在 Silverlight 2 中,您肯定能建立打包到程式集中的自訂類庫。但您應當意識到這些庫針對的是 .NET Framework 的 Silverlight 版本,並使用了不同的安全模型(有關詳細資料,請參閱本月期刊中的 CLR 全面透徹解析)。

您在案頭 WPF 應用程式中使用的所有程式集都必須重編譯為 Silverlight 類庫,以確保它們能引用正確的程式集併合法地調用類。顯然,該重編譯過程中會修複對不受支援類的調用,從而產生額外工作。

總之,只需少量的工作即可取得 WPF 應用程式,並使用 Silverlight 通過 Web 將其公開。如採取這一方式,您實際上是將代碼公開在很多非 Windows 平台上,用戶端不必安裝完整的 .NET Framework。圖 1 顯示了一個簡單的 WPF 獨立傳統型應用程式。圖 2 顯示了通過 Silverlight 在 Internet Explorer 中承載的同一應用程式。

圖 1 樣本 WPF 應用程式

圖 2 適用於 Silverlight 的 WPF 應用程式(單擊映像可查看大圖)

分析Managed 程式碼

出於某些原因,WPF 與 Silverlight 間的代碼重用被看作一個 XAML 問題。如前所述,在調整代碼時會遇到一些 XAML 問題,但最大的難題是程式碼後置類別。

在 Windows 和 Silverlight 中,XAML 檔案都是與用 C# 或其他託管語言編寫的程式碼後置類別配對的。遺憾的是,這些程式碼後置類別針對的是不同版本的 .NET Framework。案頭版本的 WPF 依賴於 .NET Framework 3.5 的完全基底類別庫 (BCL),而 Silverlight 2 使用的是輕量級版本的 BCL。

Silverlight 版本的 BCL 明顯比較小,但仍然支援一些準系統,如集合、反射、Regex、字串處理、線程和計時器。您還有一些用於調用各種服務(如 XML Web 服務、WCF服務和 ADO.NET 資料服務)的工具。此外,豐富的網路支援讓您能通過 HTTP 進行通訊-使用 Plain Old XML (POX) 和具像狀態傳輸 (REST) 服務,通常可到達任何公用 HTTP 端點。網路支援還包括(跨域)通訊端和雙工通訊。

最後,Silverlight BCL 能與 XML 資料良好協同,包括特殊版本的 XmlReader 和 XmlWriter 類。這些類與案頭版本的 .NET Framework 內的相似類極為相像。

有了這些核心功能,Silverlight 2 可完全支援 LINQ to Objects、LINQ to XML 以及運算式樹狀架構。從 Beta 2 開始,Microsoft 還將全新的 LINQ 添加到了 JavaScript Object Notation (JSON) 提供者中以直接對 JSON 資料運行 LINQ 查詢。

另一點要注意的是這種連網只能在 Silverlight 中非同步發生。要進行同步調用,您需要儘可能藉助呼叫瀏覽器互通性層並從瀏覽器實現 XMLHttpRequest(有關詳細資料,請參閱 go.microsoft.com/fwlink/?LinkId=124048)。

主要問題是 WPF 和 Silverlight 程式碼後置類別利用截然不同的類庫。這一事實比任何 XAML 差異都要妨礙可重用性。我們接下來解決該問題。

擷取用於關鍵代碼的策略

在編寫將由 Silverlight 和 Windows 運行時共用程式碼時,您應當非常瞭解各個平台的支援對象。在理想狀態下,您會得到一個在各個平台上需要特殊處理的請求工作清單。接下來,將對象中的這些程式碼片段隔離並從其中提取一個介面。

這樣一來,您將得到完全可重用的代碼塊,它可調用關鍵功能的獨立組件。之後,將此類應用程式遷移到 Silverlight(或 WPF)中就變得像用那些平台特定的其他組件替換關鍵組件一樣容易。因為所有這些組件仍公開了一個公用介面,它們的實現對於主代碼塊是透明的。

基於此方法的模式即“策略”模式。有關此方法的正式定義,請參閱 go.microsoft.com/fwlink/?LinkId=124047。簡言之,“策略”模式在您需要動態地更改那些用於在應用程式中執行某個特定任務的運算時非常有用。

當您已標識出那些可能根據運行時狀況發生變化的代碼地區時,就可以定義用於指定代碼抽象行為的介面了。接下來,建立一個或多個用於實現該介面的策略類,以表示可完成這些抽象行為的各種方法。之後再更改策略類時,您只需更改對該介面定義的運算求解的方法即可。這樣做可以將行為的實際實現(即策略)與使用它的代碼分離開來。

例如,在 ASP.NET 中,“策略”模式常用於成員、角色、使用者設定檔等提供者模型的實現中。ASP.NET 運行時知道需要處理某個特定的介面,即成員和使用者。同時還瞭解如何找到並執行個體化這些介面類型的具體類。但運行時組件僅依靠介面,這使得該具體類的詳細資料與 ASP.NET 不相關。

樣本分析

我們分析一下圖 12 中所示的應用程式範例。在 Silverlight 和 Windows 中,該應用程式都為使用者提供了一個可鍵入股票代號以獲得當前報價的表單。圖 3 顯示了該應用程式在使用者單擊按鈕時的圖示。使用者介面使用 Model-View-Presenter (MVP) 模式在單個表示器類中傳遞 UI 背景所有邏輯。而該表示器隨之調用一個內部 QuoteService 類,它通過向 StockInfo 對象提供即將合并到使用者介面中的所有資訊給出最終響應(請參閱圖 4)。

圖 3 應用程式的行為圖示(單擊映像可查看大圖)

圖 4 表示器類

複製代碼

namespace Samples{    class SymbolFinderPresenter    {        // Internal reference to the view to update        private ISymbolFinderView _view;        public SymbolFinderPresenter(ISymbolFinderView view) {            this._view = view;        }        public void Initialize() { }        // Triggered by the user's clicking on button Find        public void FindSymbol() {            // Clear the view before operations start            ClearView();            // Get the symbol to retrieve             string symbol = this._view.SymbolName;            if (String.IsNullOrEmpty(symbol)) {                _view.QuickInfoErrorMessage = "Symbol not found.";                return;            }            QuoteService service = new QuoteService();            StockInfo stock = service.GetQuote(symbol);            // Update the view            UpdateView(stock);        }         private void ClearView() {            _view.SymbolDisplayName = String.Empty;            _view.SymbolValue = String.Empty;            _view.SymbolChange = String.Empty;            _view.ServiceProviderName = String.Empty;        }        private void UpdateView(StockInfo stock) {             // Update the view            _view.QuickInfoErrorMessage = String.Empty;            _view.SymbolDisplayName = stock.Symbol;            _view.SymbolValue = stock.Quote;            _view.SymbolChange = stock.Change;            _view.ServiceProviderName = stock.ProviderName;        }     }}

QuoteService 類不會試圖自己檢索報價。它首先建立一個提供者,然後依賴它工作。QuoteService 類執行了一個非常簡單的運算。如果存在 網際網路連線,它就使用採用了公用 Web 服務的提供者類來擷取財務資料,否則將轉變為一個僅返回隨機數位偽提供者。因此有時候 QuoteService 類需要檢測 網際網路連線,如圖 5

圖 5 QuoteService 檢測 網際網路連線

複製代碼

public StockInfo GetQuote(string symbol){    // Get the provider of the service    IQuoteServiceProvider provider = ResolveProvider();    return provider.GetQuote(symbol);}private IQuoteServiceProvider ResolveProvider(){    bool isOnline = IsConnectedToInternet();    if (isOnline)        return new FinanceInfoProvider();    return new RandomProvider();}

到目前為止,Silverlight 和 WPF 之間沒有任何區別,所有的代碼均可完全重用。要檢測 .NET 中的 網際網路連線,您可以對 NetworkInterface 對象使用某些靜態方法。該對象是在 System.Net.NetworkInformation 命名空間中定義的。特別的,GetIsNetworkAvailable 方法返回了一個用於表示是否存在網路連接的布爾值。令人遺憾的是,該值並未對 網際網路連線做過多說明。要確保可訪問 Internet,唯一的方法就是嘗試 ping 主機(請參閱圖 6)。

圖 6 用 ping 檢測 網際網路連線

複製代碼

private bool IsConnectedToInternet(){    string host = "...";    bool result = false;    Ping p = new Ping();    try    {        PingReply reply = p.Send(host, 3000);        if (reply.Status == IPStatus.Success)            return true;    }    catch { }    return result;}

圖 6 中的唯一問題在於 Silverlight 2 中並不支援該代碼。(同樣,該代碼也不受 NetworkInterface 對象的支援。)您需要在可替換類中隔離此代碼(以及您發現可能出現相容性問題的其他任何代碼)。(有關此限制的詳細資料,請參閱本期隨附的 Silverlight 中 CoreCLR 側欄)。在配套的原始碼中,我為這些種可能出現問題的方法建立了工具 + 生產力介面,然後建立了實現各個平台的介面的策略類,如圖 7 所示。

圖 7 用於檢測 網際網路連線的組件

複製代碼

public partial class SilverCompatLayer : ICompatLib{    public bool IsConnectedToInternet()    {        string host = "...";        bool result = false;        Ping p = new Ping();        try        {            PingReply reply = p.Send(host, 3000);            if (reply.Status == IPStatus.Success)                return true;        }        catch { }        return result;    }    public string GetRawQuoteInfo(string symbol)    {        string output = String.Empty;        StreamReader reader = null;        // Set the URL to invoke        string url = String.Format(UrlBase, symbol);        // Connect and get response        WebRequest request = WebRequest.Create(url);        WebResponse response = request.GetResponse();        // Read the response        using (reader = new StreamReader(response.GetResponseStream()))        {           output = reader.ReadToEnd();           reader.Close();        }        return output;    }    // A few other methods that require a different implementation     // (See source code)    ...}

這裡的介面將平台特定的策略類與承載應用程式分離開來。那時就可以隱藏在 Factory 方法後執行個體化具體策略類的代碼,下列代碼可安全地在 WPF 和 Silverlight 中使用:

複製代碼

private bool IsConnectedToInternet(){    ICompatLib layer = ServiceResolver.ResolveCompatLayer();    return layer.IsConnectedToInternet();}

SilverCompatLayer 類存在於一個獨立的程式集中。將 WPF 代碼遷移到 Silverlight 中時,您需要更改的就是此程式集,反之亦然。

建立完必要的平台特定的策略類後,剩餘的所有任務就是建立該應用程式的 Silverlight 版本。派生自原始 WPF 應用程式的 Silverlight 項目中包含除相容性程式集以外的所有檔案的完全重複項。

在本文關聯的下載代碼中,您將注意到我通過在 Factory 方法中顯式執行個體化具體類來確定使用哪種策略實現。您可以像這樣地直接執行個體化類,或從設定檔中讀取它們的名稱,然後使用反射擷取執行個體。這些實現細節主要取決於您當前構建的系統類別型。就涉及到的 WPF-to-Silverlight 相容性而言,策略和分層是需要理解的關鍵概念。

最終注意事項

在應用程式範例中,我進行了一次網路調用。在原始的 WPF 應用程式中,該調用為同步調用。但在 Silverlight 2 中,根本不支援同步網路調用。進行同步調用的唯一方法就是調用 XMLHttpRequest 的瀏覽器實現(有關詳細資料,請參閱原始碼)。

在範例程式碼中,我成功地將原始 WPF 應用程式遷移到了 Web 上。遷移代碼時,您可能希望考慮到 Silverlight 環境的本機功能並在調整時修改應用程式的結構。請注意,在我的簡單樣本中,使用 Silverlight 編程模型重新編寫該應用程式比根據 WPF 應用程式進行改編所花費的代碼和工作都要少許多。

因此,當 WPF 和 Silverlight 在純粹的可視程式設計語言(如 XAML)領域有很多共同之處。但基礎編程模型就稍有不同,在 WPF 中有效解決方案不一定就適合於 Silverlight,反之亦然。這就是說,在 WPF 與 Silverlight 應用程式之間的 XAML 和代碼共用一定可以實現。

請將您想向 Dino 詢問的問題和提出的意見發送至 cutting@microsoft.com。

Dino Esposito 目前是 IDesign 的架構師,也是《Programming ASP.NET 3.5 Core Reference》的作者。Dino 定居於意大利,經常在世界各地的業內活動中發表演講。您可加入他的部落格,網址為 weblogs.asp.net/despos。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.